Compare commits
20 Commits
458e467a14
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b6211cf5f | |||
| c7f2c3fdfe | |||
| f30c93ffce | |||
| 3e6bbe9a93 | |||
| c6cbb02dfa | |||
| bec1916ccc | |||
| ab17e9e758 | |||
| f257e5428f | |||
| 797411ccc3 | |||
| 94a609e438 | |||
| 409ac3fea7 | |||
| b3876666c8 | |||
| bd1a61e9cd | |||
| f2ce9ec262 | |||
| eddfa3a1f1 | |||
| 1e77914314 | |||
| 52dfbb3870 | |||
| 72e85b99ee | |||
| c7807610f6 | |||
| 81f0dd88a6 |
@@ -104,6 +104,39 @@ jobs:
|
||||
echo "short_sha=$SHORT_SHA"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# ⏳ Wait for Upstream Packages/Images if Tagged
|
||||
if [[ "${{ github.ref_type }}" == "tag" ]]; then
|
||||
echo "🔎 Checking for @mintel dependencies in package.json..."
|
||||
# Extract any @mintel/ version (they should be synced in monorepo)
|
||||
UPSTREAM_VERSION=$(grep -o '"@mintel/.*": "[^"]*"' package.json | grep -v "next-utils" | cut -d'"' -f4 | sed 's/\^//; s/\~//' | sort -V | tail -1)
|
||||
TAG_TO_WAIT="v$UPSTREAM_VERSION"
|
||||
|
||||
if [[ -n "$UPSTREAM_VERSION" && "$UPSTREAM_VERSION" != "workspace:"* ]]; then
|
||||
# 1. Discovery (Works without token for public repositories)
|
||||
UPSTREAM_SHA=$(git ls-remote --tags https://git.infra.mintel.me/mmintel/at-mintel.git "$TAG_TO_WAIT" | grep "$TAG_TO_WAIT" | tail -n1 | awk '{print $1}')
|
||||
|
||||
if [[ -z "$UPSTREAM_SHA" ]]; then
|
||||
echo "❌ Error: Tag $TAG_TO_WAIT not found in mmintel/at-mintel."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Tag verified: Found upstream SHA $UPSTREAM_SHA for $TAG_TO_WAIT"
|
||||
|
||||
# 2. Status Check (Requires GITEA_PAT for cross-repo API access)
|
||||
POLL_TOKEN="${{ secrets.GITEA_PAT || secrets.MINTEL_PRIVATE_TOKEN }}"
|
||||
|
||||
if [[ -n "$POLL_TOKEN" ]]; then
|
||||
echo "⏳ GITEA_PAT found. Checking upstream build status..."
|
||||
curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
"https://git.infra.mintel.me/mmintel/at-mintel/raw/branch/main/packages/infra/scripts/wait-for-upstream.sh" > wait-for-upstream.sh
|
||||
chmod +x wait-for-upstream.sh
|
||||
GITEA_TOKEN="$POLL_TOKEN" ./wait-for-upstream.sh "mmintel/at-mintel" "$TAG_TO_WAIT"
|
||||
else
|
||||
echo "ℹ️ No GITEA_PAT secret found. Skipping build status wait (Actions API is restricted)."
|
||||
echo " If this build fails, ensure that mmintel/at-mintel $TAG_TO_WAIT has finished its Docker build."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# JOB 2: QA (Lint, Typecheck, Test)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
@@ -188,6 +221,7 @@ jobs:
|
||||
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
|
||||
DIRECTUS_URL: ${{ needs.prepare.outputs.directus_url }}
|
||||
DIRECTUS_HOST: cms.${{ needs.prepare.outputs.traefik_host }}
|
||||
TRAEFIK_HOST: ${{ needs.prepare.outputs.traefik_host }}
|
||||
|
||||
# Secrets mapping (Directus)
|
||||
DIRECTUS_KEY: ${{ (needs.prepare.outputs.target == 'testing' && secrets.TESTING_DIRECTUS_KEY) || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_DIRECTUS_KEY) || secrets.DIRECTUS_KEY || vars.DIRECTUS_KEY }}
|
||||
@@ -221,14 +255,31 @@ jobs:
|
||||
TRAEFIK_RULE: ${{ needs.prepare.outputs.traefik_rule }}
|
||||
ENV_FILE: ${{ needs.prepare.outputs.env_file }}
|
||||
run: |
|
||||
# Generate Environment File
|
||||
# Middleware Selection Logic
|
||||
# Regular app routes get auth on non-production
|
||||
# Unprotected routes (/stats, /errors) never get auth
|
||||
LOG_LEVEL=$( [[ "$TARGET" == "testing" || "$TARGET" == "development" ]] && echo "debug" || echo "info" )
|
||||
COOKIE_DOMAIN=.$(echo $NEXT_PUBLIC_BASE_URL | sed 's|https://||')
|
||||
STD_MW="${PROJECT_NAME}-ratelimit,${PROJECT_NAME}-forward,${PROJECT_NAME}-compress"
|
||||
|
||||
if [[ "$TARGET" == "production" ]]; then
|
||||
AUTH_MIDDLEWARE="$STD_MW"
|
||||
COMPOSE_PROFILES=""
|
||||
else
|
||||
# Order: Ratelimit -> Forward (Proto) -> Auth -> Compression
|
||||
AUTH_MIDDLEWARE="${PROJECT_NAME}-ratelimit,${PROJECT_NAME}-forward,${PROJECT_NAME}-auth,${PROJECT_NAME}-compress"
|
||||
COMPOSE_PROFILES="gatekeeper"
|
||||
fi
|
||||
AUTH_MIDDLEWARE_UNPROTECTED="$STD_MW"
|
||||
|
||||
# Gatekeeper Origin
|
||||
GATEKEEPER_ORIGIN="$NEXT_PUBLIC_BASE_URL/gatekeeper"
|
||||
|
||||
cat > .env.deploy << EOF
|
||||
# Generated by CI - $TARGET
|
||||
IMAGE_TAG=$IMAGE_TAG
|
||||
NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
|
||||
GATEKEEPER_ORIGIN=$GATEKEEPER_ORIGIN
|
||||
SENTRY_DSN=$SENTRY_DSN
|
||||
LOG_LEVEL=$LOG_LEVEL
|
||||
MAIL_HOST=$MAIL_HOST
|
||||
@@ -263,21 +314,13 @@ jobs:
|
||||
SENTRY_ENVIRONMENT=$TARGET
|
||||
PROJECT_NAME=$PROJECT_NAME
|
||||
TRAEFIK_HOST_RULE='$TRAEFIK_RULE'
|
||||
TRAEFIK_HOST=$TRAEFIK_HOST
|
||||
ENV_FILE=$ENV_FILE
|
||||
COMPOSE_PROFILES=$COMPOSE_PROFILES
|
||||
AUTH_MIDDLEWARE=$AUTH_MIDDLEWARE
|
||||
AUTH_MIDDLEWARE_UNPROTECTED=$AUTH_MIDDLEWARE_UNPROTECTED
|
||||
EOF
|
||||
|
||||
# Middleware Selection Logic
|
||||
# Regular app routes get auth on non-production
|
||||
# Unprotected routes (/stats, /errors) never get auth
|
||||
STD_MW="${PROJECT_NAME}-ratelimit,${PROJECT_NAME}-forward,${PROJECT_NAME}-compress"
|
||||
|
||||
if [[ "$TARGET" == "production" ]]; then
|
||||
printf "AUTH_MIDDLEWARE=%s\n" "$STD_MW" >> .env.deploy
|
||||
else
|
||||
printf "AUTH_MIDDLEWARE=%s\n" "${PROJECT_NAME}-auth,$STD_MW" >> .env.deploy
|
||||
fi
|
||||
printf "AUTH_MIDDLEWARE_UNPROTECTED=%s\n" "$STD_MW" >> .env.deploy
|
||||
|
||||
- name: 🚀 SSH Deploy
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
@@ -66,7 +66,8 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
alt={featuredPost.frontmatter.title}
|
||||
fill
|
||||
className="absolute inset-0 w-full h-full object-cover scale-105 animate-slow-zoom opacity-40 md:opacity-60"
|
||||
unoptimized
|
||||
sizes="100vw"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 image-overlay-gradient" />
|
||||
</>
|
||||
@@ -153,7 +154,7 @@ export default async function BlogIndex({ params }: BlogIndexProps) {
|
||||
alt={post.frontmatter.title}
|
||||
fill
|
||||
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
|
||||
unoptimized
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
/>
|
||||
<div className="absolute inset-0 image-overlay-gradient opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
{post.frontmatter.category && (
|
||||
|
||||
@@ -12,6 +12,13 @@ import '../../styles/globals.css';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import { config } from '@/lib/config';
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
import { Inter } from 'next/font/google';
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
variable: '--font-inter',
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(SITE_URL),
|
||||
@@ -88,7 +95,7 @@ export default async function LocaleLayout({
|
||||
}
|
||||
|
||||
return (
|
||||
<html lang={safeLocale} className="scroll-smooth overflow-x-hidden">
|
||||
<html lang={safeLocale} className={`scroll-smooth overflow-x-hidden ${inter.variable}`}>
|
||||
<body className="flex flex-col min-h-screen font-sans selection:bg-accent selection:text-primary-dark antialiased overflow-x-hidden">
|
||||
<NextIntlClientProvider messages={messages} locale={safeLocale}>
|
||||
<JsonLd />
|
||||
|
||||
@@ -3,14 +3,16 @@ import JsonLd from '@/components/JsonLd';
|
||||
import { getBreadcrumbSchema, SITE_URL } from '@/lib/schema';
|
||||
import ProductCategories from '@/components/home/ProductCategories';
|
||||
import WhatWeDo from '@/components/home/WhatWeDo';
|
||||
import RecentPosts from '@/components/home/RecentPosts';
|
||||
import Experience from '@/components/home/Experience';
|
||||
import WhyChooseUs from '@/components/home/WhyChooseUs';
|
||||
import MeetTheTeam from '@/components/home/MeetTheTeam';
|
||||
import GallerySection from '@/components/home/GallerySection';
|
||||
import VideoSection from '@/components/home/VideoSection';
|
||||
import CTA from '@/components/home/CTA';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Reveal from '@/components/Reveal';
|
||||
|
||||
const RecentPosts = dynamic(() => import('@/components/home/RecentPosts'));
|
||||
const Experience = dynamic(() => import('@/components/home/Experience'));
|
||||
const WhyChooseUs = dynamic(() => import('@/components/home/WhyChooseUs'));
|
||||
const MeetTheTeam = dynamic(() => import('@/components/home/MeetTheTeam'));
|
||||
const GallerySection = dynamic(() => import('@/components/home/GallerySection'));
|
||||
const VideoSection = dynamic(() => import('@/components/home/VideoSection'));
|
||||
const CTA = dynamic(() => import('@/components/home/CTA'));
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server';
|
||||
import { Metadata } from 'next';
|
||||
import { getOGImageMetadata } from '@/lib/metadata';
|
||||
@@ -49,7 +51,7 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
|
||||
<Reveal>
|
||||
<VideoSection />
|
||||
</Reveal>
|
||||
<Reveal>
|
||||
<Reveal className="content-visibility-auto">
|
||||
<CTA />
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
@@ -243,6 +243,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
||||
alt={product.frontmatter.title}
|
||||
fill
|
||||
className="object-contain p-8 transition-transform duration-700 group-hover:scale-110 z-10"
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
/>
|
||||
{/* Subtle reflection/shadow effect */}
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 w-2/3 h-4 bg-black/5 blur-xl rounded-full" />
|
||||
|
||||
@@ -143,8 +143,7 @@ export default async function ProductsPage({ params }: ProductsPageProps) {
|
||||
alt={category.title}
|
||||
fill
|
||||
className="object-cover transition-transform duration-1000 group-hover:scale-105"
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
unoptimized
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw"
|
||||
/>
|
||||
<div className="absolute inset-0 image-overlay-gradient opacity-80 group-hover:opacity-90 transition-opacity duration-500" />
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ export default async function TeamPage({ params }: TeamPageProps) {
|
||||
alt="KLZ Team"
|
||||
fill
|
||||
className="object-cover scale-105 animate-slow-zoom opacity-30 md:opacity-40"
|
||||
sizes="100vw"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-primary-dark/80 via-primary-dark/40 to-primary-dark/80" />
|
||||
|
||||
@@ -10,31 +10,35 @@ export default function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="bg-primary text-white py-24 relative overflow-hidden">
|
||||
<footer className="bg-primary text-white py-24 relative overflow-hidden content-visibility-auto">
|
||||
<div className="absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-white/20 to-transparent" />
|
||||
|
||||
|
||||
<Container>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-12 gap-16 mb-20">
|
||||
{/* Brand Column */}
|
||||
<div className="lg:col-span-4 space-y-8">
|
||||
<Link href={`/${locale}`} className="inline-block group">
|
||||
<Image
|
||||
src="/logo-white.svg"
|
||||
alt={t('products')}
|
||||
width={150}
|
||||
height={40}
|
||||
<Image
|
||||
src="/logo-white.svg"
|
||||
alt={t('products')}
|
||||
width={150}
|
||||
height={40}
|
||||
className="h-10 w-auto transition-transform duration-500 group-hover:scale-110"
|
||||
unoptimized
|
||||
/>
|
||||
</Link>
|
||||
<p className="text-white/60 text-base md:text-lg leading-relaxed max-w-sm">
|
||||
{t('tagline')}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<a href="https://www.linkedin.com/company/klz-vertriebs-gmbh/" target="_blank" rel="noopener noreferrer" className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center text-white hover:bg-accent hover:text-primary-dark transition-all duration-300 border border-white/10">
|
||||
<a
|
||||
href="https://www.linkedin.com/company/klz-vertriebs-gmbh/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center text-white hover:bg-accent hover:text-primary-dark transition-all duration-300 border border-white/10"
|
||||
>
|
||||
<span className="sr-only">LinkedIn</span>
|
||||
<svg className="w-5 h-5 fill-current" viewBox="0 0 24 24">
|
||||
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/>
|
||||
<path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
@@ -42,52 +46,113 @@ export default function Footer() {
|
||||
|
||||
{/* Links Columns */}
|
||||
<div className="lg:col-span-2">
|
||||
<h4 className="text-accent font-bold uppercase tracking-widest text-xs md:text-sm mb-8">{t('legal')}</h4>
|
||||
<h4 className="text-accent font-bold uppercase tracking-widest text-xs md:text-sm mb-8">
|
||||
{t('legal')}
|
||||
</h4>
|
||||
<ul className="space-y-4 text-white/70 list-none m-0 p-0">
|
||||
<li><Link href={`/${locale}/${t('legalNoticeSlug')}`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{t('legalNotice')}</Link></li>
|
||||
<li><Link href={`/${locale}/${t('privacyPolicySlug')}`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{t('privacyPolicy')}</Link></li>
|
||||
<li><Link href={`/${locale}/${t('termsSlug')}`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{t('terms')}</Link></li>
|
||||
<li>
|
||||
<Link
|
||||
href={`/${locale}/${t('legalNoticeSlug')}`}
|
||||
className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block"
|
||||
>
|
||||
{t('legalNotice')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={`/${locale}/${t('privacyPolicySlug')}`}
|
||||
className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block"
|
||||
>
|
||||
{t('privacyPolicy')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={`/${locale}/${t('termsSlug')}`}
|
||||
className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block"
|
||||
>
|
||||
{t('terms')}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-2">
|
||||
<h4 className="text-accent font-bold uppercase tracking-widest text-xs md:text-sm mb-8">{t('company')}</h4>
|
||||
<h4 className="text-accent font-bold uppercase tracking-widest text-xs md:text-sm mb-8">
|
||||
{t('company')}
|
||||
</h4>
|
||||
<ul className="space-y-4 text-white/70 list-none m-0 p-0">
|
||||
<li><Link href={`/${locale}/team`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{navT('team')}</Link></li>
|
||||
<li><Link href={`/${locale}/products`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{navT('products')}</Link></li>
|
||||
<li><Link href={`/${locale}/blog`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{navT('blog')}</Link></li>
|
||||
<li><Link href={`/${locale}/contact`} className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block">{navT('contact')}</Link></li>
|
||||
<li>
|
||||
<Link
|
||||
href={`/${locale}/team`}
|
||||
className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block"
|
||||
>
|
||||
{navT('team')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={`/${locale}/products`}
|
||||
className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block"
|
||||
>
|
||||
{navT('products')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={`/${locale}/blog`}
|
||||
className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block"
|
||||
>
|
||||
{navT('blog')}
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href={`/${locale}/contact`}
|
||||
className="text-white/70 hover:text-accent transition-all duration-300 hover:translate-x-1 inline-block"
|
||||
>
|
||||
{navT('contact')}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Recent Posts Column */}
|
||||
<div className="lg:col-span-4">
|
||||
<h4 className="text-accent font-bold uppercase tracking-widest text-xs md:text-sm mb-8">{t('recentPosts')}</h4>
|
||||
<h4 className="text-accent font-bold uppercase tracking-widest text-xs md:text-sm mb-8">
|
||||
{t('recentPosts')}
|
||||
</h4>
|
||||
<ul className="space-y-6 list-none m-0 p-0">
|
||||
{[
|
||||
{
|
||||
title: locale === 'de'
|
||||
? "Windparkbau im Fokus: drei typische Kabelherausforderungen"
|
||||
: "Focus on wind farm construction: three typical cable challenges",
|
||||
slug: locale === 'de'
|
||||
? "windparkbau-im-fokus-drei-typische-kabelherausforderungen"
|
||||
: "focus-on-wind-farm-construction-three-typical-cable-challenges"
|
||||
title:
|
||||
locale === 'de'
|
||||
? 'Windparkbau im Fokus: drei typische Kabelherausforderungen'
|
||||
: 'Focus on wind farm construction: three typical cable challenges',
|
||||
slug:
|
||||
locale === 'de'
|
||||
? 'windparkbau-im-fokus-drei-typische-kabelherausforderungen'
|
||||
: 'focus-on-wind-farm-construction-three-typical-cable-challenges',
|
||||
},
|
||||
{
|
||||
title: locale === 'de'
|
||||
? "Warum das N2XS(F)2Y das ideale Kabel für Ihr Energieprojekt ist"
|
||||
: "Why the N2XS(F)2Y is the ideal cable for your energy project",
|
||||
slug: locale === 'de'
|
||||
? "n2xsf2y-mittelspannungskabel-energieprojekt"
|
||||
: "why-the-n2xsf2y-is-the-ideal-cable-for-your-energy-project"
|
||||
}
|
||||
title:
|
||||
locale === 'de'
|
||||
? 'Warum das N2XS(F)2Y das ideale Kabel für Ihr Energieprojekt ist'
|
||||
: 'Why the N2XS(F)2Y is the ideal cable for your energy project',
|
||||
slug:
|
||||
locale === 'de'
|
||||
? 'n2xsf2y-mittelspannungskabel-energieprojekt'
|
||||
: 'why-the-n2xsf2y-is-the-ideal-cable-for-your-energy-project',
|
||||
},
|
||||
].map((post, i) => (
|
||||
<li key={i}>
|
||||
<Link href={`/${locale}/blog/${post.slug}`} className="group block text-white/80">
|
||||
<p className="text-white/80 font-bold group-hover:text-accent transition-colors leading-snug mb-2 text-base md:text-base">
|
||||
{post.title}
|
||||
</p>
|
||||
<span className="text-xs text-white/40 uppercase tracking-widest">{t('readArticle')} →</span>
|
||||
<span className="text-xs text-white/40 uppercase tracking-widest">
|
||||
{t('readArticle')} →
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
@@ -98,8 +163,12 @@ export default function Footer() {
|
||||
<div className="pt-12 border-t border-white/10 flex flex-col md:flex-row justify-between items-center gap-8 text-white/40 text-xs md:text-sm font-medium">
|
||||
<p>{t('copyright', { year: currentYear })}</p>
|
||||
<div className="flex gap-8">
|
||||
<Link href="/en" locale="en" className="hover:text-white transition-colors">English</Link>
|
||||
<Link href="/de" locale="de" className="hover:text-white transition-colors">Deutsch</Link>
|
||||
<Link href="/en" locale="en" className="hover:text-white transition-colors">
|
||||
English
|
||||
</Link>
|
||||
<Link href="/de" locale="de" className="hover:text-white transition-colors">
|
||||
Deutsch
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -87,7 +87,6 @@ export default function Header() {
|
||||
height={120}
|
||||
className="h-10 md:h-14 w-auto transition-all duration-500 group-hover:scale-110"
|
||||
priority
|
||||
unoptimized
|
||||
/>
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
@@ -9,17 +9,17 @@ export default function Experience() {
|
||||
return (
|
||||
<Section className="relative py-32 md:py-48 overflow-hidden text-white">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="/uploads/2024/12/1694273920124-copy-2.webp"
|
||||
alt={t('subtitle')}
|
||||
<Image
|
||||
src="/uploads/2024/12/1694273920124-copy-2.webp"
|
||||
alt={t('subtitle')}
|
||||
fill
|
||||
className="object-cover object-center scale-105 animate-slow-zoom"
|
||||
unoptimized
|
||||
sizes="100vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-primary/80 mix-blend-multiply" />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-primary via-primary/40 to-transparent" />
|
||||
</div>
|
||||
|
||||
|
||||
<Container className="relative z-10">
|
||||
<div className="max-w-3xl">
|
||||
<Heading level={2} subtitle={t('subtitle')} className="text-white">
|
||||
@@ -29,19 +29,25 @@ export default function Experience() {
|
||||
<p className="border-l-4 border-accent pl-8 py-2 bg-white/5 backdrop-blur-sm rounded-r-xl">
|
||||
{t('p1')}
|
||||
</p>
|
||||
<p className="pl-9">
|
||||
{t('p2')}
|
||||
</p>
|
||||
<p className="pl-9">{t('p2')}</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="mt-16 grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
<div className="animate-fade-in">
|
||||
<div className="text-2xl md:text-3xl font-extrabold text-accent mb-4">{t('certifiedQuality')}</div>
|
||||
<div className="text-base md:text-lg font-bold uppercase tracking-widest text-white/60">{t('vdeApproved')}</div>
|
||||
<div className="text-2xl md:text-3xl font-extrabold text-accent mb-4">
|
||||
{t('certifiedQuality')}
|
||||
</div>
|
||||
<div className="text-base md:text-lg font-bold uppercase tracking-widest text-white/60">
|
||||
{t('vdeApproved')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="animate-fade-in" style={{ animationDelay: '100ms' }}>
|
||||
<div className="text-2xl md:text-3xl font-extrabold text-accent mb-4">{t('fullSpectrum')}</div>
|
||||
<div className="text-base md:text-lg font-bold uppercase tracking-widest text-white/60">{t('solutionsRange')}</div>
|
||||
<div className="text-2xl md:text-3xl font-extrabold text-accent mb-4">
|
||||
{t('fullSpectrum')}
|
||||
</div>
|
||||
<div className="text-base md:text-lg font-bold uppercase tracking-widest text-white/60">
|
||||
{t('solutionsRange')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function GallerySection() {
|
||||
alt={`${t('alt')} ${idx + 1}`}
|
||||
fill
|
||||
className="object-cover transition-transform duration-1000 group-hover:scale-110"
|
||||
unoptimized
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-primary-dark/20 group-hover:bg-transparent transition-all duration-500" />
|
||||
<div className="absolute inset-0 border-0 group-hover:border-[16px] border-white/10 transition-all duration-500 pointer-events-none" />
|
||||
|
||||
@@ -4,7 +4,8 @@ import Scribble from '@/components/Scribble';
|
||||
import { Button, Container, Heading, Section } from '@/components/ui';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import HeroIllustration from './HeroIllustration';
|
||||
import dynamic from 'next/dynamic';
|
||||
const HeroIllustration = dynamic(() => import('./HeroIllustration'), { ssr: false });
|
||||
|
||||
export default function Hero() {
|
||||
const t = useTranslations('Home.hero');
|
||||
@@ -19,7 +20,10 @@ export default function Hero() {
|
||||
variants={containerVariants}
|
||||
>
|
||||
<motion.div variants={headingVariants}>
|
||||
<Heading level={1} className="text-center md:text-left mb-6 md:mb-8 md:max-w-none text-white text-4xl sm:text-5xl md:text-7xl font-extrabold [text-shadow:_-2px_-2px_0_#002b49,_2px_-2px_0_#002b49,_-2px_2px_0_#002b49,_2px_2px_0_#002b49,_-2px_0_0_#002b49,_2px_0_0_#002b49,_0_-2px_0_#002b49,_0_2px_0_#002b49]">
|
||||
<Heading
|
||||
level={1}
|
||||
className="text-center md:text-left mb-6 md:mb-8 md:max-w-none text-white text-4xl sm:text-5xl md:text-7xl font-extrabold [text-shadow:_-2px_-2px_0_#002b49,_2px_-2px_0_#002b49,_-2px_2px_0_#002b49,_2px_2px_0_#002b49,_-2px_0_0_#002b49,_2px_0_0_#002b49,_0_-2px_0_#002b49,_0_2px_0_#002b49]"
|
||||
>
|
||||
{t.rich('title', {
|
||||
green: (chunks) => (
|
||||
<span className="relative inline-block">
|
||||
@@ -36,7 +40,7 @@ export default function Hero() {
|
||||
<Scribble variant="circle" />
|
||||
</motion.div>
|
||||
</span>
|
||||
)
|
||||
),
|
||||
})}
|
||||
</Heading>
|
||||
</motion.div>
|
||||
@@ -50,13 +54,23 @@ export default function Hero() {
|
||||
variants={buttonContainerVariants}
|
||||
>
|
||||
<motion.div variants={buttonVariants}>
|
||||
<Button href="/contact" variant="accent" size="lg" className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg">
|
||||
<Button
|
||||
href="/contact"
|
||||
variant="accent"
|
||||
size="lg"
|
||||
className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg"
|
||||
>
|
||||
{t('cta')}
|
||||
<span className="transition-transform group-hover/btn:translate-x-1">→</span>
|
||||
</Button>
|
||||
</motion.div>
|
||||
<motion.div variants={buttonVariants}>
|
||||
<Button href="/products" variant="white" size="lg" className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg md:bg-white md:text-primary md:hover:bg-neutral-light md:border-none">
|
||||
<Button
|
||||
href="/products"
|
||||
variant="white"
|
||||
size="lg"
|
||||
className="group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg md:bg-white md:text-primary md:hover:bg-neutral-light md:border-none"
|
||||
>
|
||||
{t('exploreProducts')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
@@ -77,7 +91,7 @@ export default function Hero() {
|
||||
className="absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 hidden sm:block"
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, ease: "easeOut", delay: 3 }}
|
||||
transition={{ duration: 1, ease: 'easeOut', delay: 3 }}
|
||||
>
|
||||
<div className="w-6 h-10 border-2 border-white/30 rounded-full flex justify-center p-1">
|
||||
<motion.div
|
||||
@@ -86,7 +100,7 @@ export default function Hero() {
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut"
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -101,9 +115,9 @@ const containerVariants = {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.12,
|
||||
delayChildren: 0.4
|
||||
}
|
||||
}
|
||||
delayChildren: 0.4,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const headingVariants = {
|
||||
@@ -112,8 +126,8 @@ const headingVariants = {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: { duration: 1.2, ease: [0.25, 0.46, 0.45, 0.94] }
|
||||
}
|
||||
transition: { duration: 1.2, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
},
|
||||
} as const;
|
||||
|
||||
const accentVariants = {
|
||||
@@ -122,8 +136,8 @@ const accentVariants = {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] }
|
||||
}
|
||||
transition: { duration: 0.8, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
},
|
||||
} as const;
|
||||
|
||||
const scribbleVariants = {
|
||||
@@ -132,8 +146,8 @@ const scribbleVariants = {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
rotate: 0,
|
||||
transition: { duration: 1, type: "spring", stiffness: 300, damping: 20 }
|
||||
}
|
||||
transition: { duration: 1, type: 'spring', stiffness: 300, damping: 20 },
|
||||
},
|
||||
} as const;
|
||||
|
||||
const subtitleVariants = {
|
||||
@@ -142,8 +156,8 @@ const subtitleVariants = {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: { duration: 1, ease: [0.25, 0.46, 0.45, 0.94] }
|
||||
}
|
||||
transition: { duration: 1, ease: [0.25, 0.46, 0.45, 0.94] },
|
||||
},
|
||||
} as const;
|
||||
|
||||
const buttonContainerVariants = {
|
||||
@@ -152,9 +166,9 @@ const buttonContainerVariants = {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.15,
|
||||
delayChildren: 0.4
|
||||
}
|
||||
}
|
||||
delayChildren: 0.4,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const buttonVariants = {
|
||||
@@ -163,6 +177,6 @@ const buttonVariants = {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
scale: 1,
|
||||
transition: { type: "spring", stiffness: 400, damping: 20 }
|
||||
}
|
||||
transition: { type: 'spring', stiffness: 400, damping: 20 },
|
||||
},
|
||||
} as const;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,43 +11,53 @@ export default function MeetTheTeam() {
|
||||
return (
|
||||
<Section className="relative py-32 md:py-48 overflow-hidden">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC08036-Large.webp"
|
||||
alt={t('subtitle')}
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC08036-Large.webp"
|
||||
alt={t('subtitle')}
|
||||
fill
|
||||
className="object-cover scale-105 animate-slow-zoom"
|
||||
unoptimized
|
||||
sizes="100vw"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-primary/70 mix-blend-multiply" />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-primary via-primary/20 to-transparent" />
|
||||
</div>
|
||||
|
||||
|
||||
<Container className="relative z-10">
|
||||
<div className="max-w-3xl text-white animate-slide-up">
|
||||
<Heading level={2} subtitle={t('subtitle')} className="text-white mb-8">
|
||||
<span className="text-white">{t('title')}</span>
|
||||
</Heading>
|
||||
|
||||
|
||||
<div className="relative mb-12">
|
||||
<div className="absolute -left-8 top-0 bottom-0 w-1 bg-accent rounded-full" />
|
||||
<p className="text-xl md:text-2xl leading-relaxed font-medium italic text-white/90 pl-8">
|
||||
"{t('description')}"
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex flex-wrap gap-8 items-center">
|
||||
<Button href={`/${locale}/team`} variant="accent" size="xl" className="group">
|
||||
{t('cta')}
|
||||
<span className="ml-3 transition-transform group-hover:translate-x-2">→</span>
|
||||
</Button>
|
||||
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex -space-x-4">
|
||||
<div className="w-14 h-14 rounded-full border-4 border-primary overflow-hidden relative">
|
||||
<Image src="/uploads/2024/12/DSC07768-Large.webp" alt={teamT('michael.name')} fill className="object-cover" />
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC07768-Large.webp"
|
||||
alt={teamT('michael.name')}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-14 h-14 rounded-full border-4 border-primary overflow-hidden relative">
|
||||
<Image src="/uploads/2024/12/DSC07963-Large.webp" alt={teamT('klaus.name')} fill className="object-cover" />
|
||||
<Image
|
||||
src="/uploads/2024/12/DSC07963-Large.webp"
|
||||
alt={teamT('klaus.name')}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-white/60 font-bold text-xs md:text-sm uppercase tracking-widest">
|
||||
|
||||
@@ -9,62 +9,74 @@ export default function ProductCategories() {
|
||||
const locale = useLocale();
|
||||
|
||||
const categories = [
|
||||
{
|
||||
title: t('categories.lowVoltage.title'),
|
||||
{
|
||||
title: t('categories.lowVoltage.title'),
|
||||
desc: t('categories.lowVoltage.description'),
|
||||
img: '/uploads/2024/11/low-voltage-category.webp',
|
||||
icon: '/uploads/2024/11/Low-Voltage.svg',
|
||||
href: `/${locale}/products/low-voltage-cables`
|
||||
href: `/${locale}/products/low-voltage-cables`,
|
||||
},
|
||||
{
|
||||
title: t('categories.mediumVoltage.title'),
|
||||
{
|
||||
title: t('categories.mediumVoltage.title'),
|
||||
desc: t('categories.mediumVoltage.description'),
|
||||
img: '/uploads/2024/11/medium-voltage-category.webp',
|
||||
icon: '/uploads/2024/11/Medium-Voltage.svg',
|
||||
href: `/${locale}/products/medium-voltage-cables`
|
||||
href: `/${locale}/products/medium-voltage-cables`,
|
||||
},
|
||||
{
|
||||
title: t('categories.highVoltage.title'),
|
||||
{
|
||||
title: t('categories.highVoltage.title'),
|
||||
desc: t('categories.highVoltage.description'),
|
||||
img: '/uploads/2024/11/high-voltage-category.webp',
|
||||
icon: '/uploads/2024/11/High-Voltage.svg',
|
||||
href: `/${locale}/products/high-voltage-cables`
|
||||
href: `/${locale}/products/high-voltage-cables`,
|
||||
},
|
||||
{
|
||||
title: t('categories.solar.title'),
|
||||
{
|
||||
title: t('categories.solar.title'),
|
||||
desc: t('categories.solar.description'),
|
||||
img: '/uploads/2024/11/solar-category.webp',
|
||||
icon: '/uploads/2024/11/Solar.svg',
|
||||
href: `/${locale}/products/solar-cables`
|
||||
}
|
||||
href: `/${locale}/products/solar-cables`,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Section className="bg-neutral-light py-0 md:py-0 lg:py-0 -mt-px">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4">
|
||||
{categories.map((category, idx) => (
|
||||
<Link key={idx} href={category.href} className="group block relative h-[400px] md:h-[500px] lg:h-[650px] overflow-hidden border-b md:border-b-0 md:border-r border-white/10 last:border-0">
|
||||
<Image
|
||||
src={category.img}
|
||||
<Link
|
||||
key={idx}
|
||||
href={category.href}
|
||||
className="group block relative h-[400px] md:h-[500px] lg:h-[650px] overflow-hidden border-b md:border-b-0 md:border-r border-white/10 last:border-0"
|
||||
>
|
||||
<Image
|
||||
src={category.img}
|
||||
alt={category.title}
|
||||
fill
|
||||
className="object-cover transition-transform duration-1000 group-hover:scale-110"
|
||||
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 25vw"
|
||||
unoptimized
|
||||
/>
|
||||
<div className="absolute inset-0 bg-primary-dark/40 group-hover:bg-primary-dark/60 transition-all duration-500" />
|
||||
<div className="absolute inset-0 p-8 md:p-10 flex flex-col justify-end text-white">
|
||||
<div className="mb-4 md:mb-6 transform transition-all duration-500 group-hover:-translate-y-4">
|
||||
<div className="w-12 h-12 md:w-16 md:h-16 bg-white/10 backdrop-blur-md rounded-xl flex items-center justify-center mb-4 md:mb-6 border border-white/20">
|
||||
<Image src={category.icon} alt="" width={40} height={40} className="w-8 h-8 md:w-10 md:h-10 brightness-0 invert" unoptimized />
|
||||
<Image
|
||||
src={category.icon}
|
||||
alt=""
|
||||
width={40}
|
||||
height={40}
|
||||
className="w-8 h-8 md:w-10 md:h-10 brightness-0 invert"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="text-xl md:text-2xl font-bold mb-2 md:mb-4 leading-tight">{category.title}</h3>
|
||||
<h3 className="text-xl md:text-2xl font-bold mb-2 md:mb-4 leading-tight">
|
||||
{category.title}
|
||||
</h3>
|
||||
<p className="text-white/80 text-sm md:text-base line-clamp-3 opacity-100 md:opacity-0 group-hover:opacity-100 transition-all duration-500 max-h-24 md:max-h-0 group-hover:max-h-32">
|
||||
{category.desc}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center text-accent font-bold tracking-wider uppercase text-xs md:text-xs opacity-100 md:opacity-0 group-hover:opacity-100 transition-all duration-500 delay-100">
|
||||
{t('exploreCategory')} <span className="ml-2 transition-transform group-hover:translate-x-2">→</span>
|
||||
{t('exploreCategory')}{' '}
|
||||
<span className="ml-2 transition-transform group-hover:translate-x-2">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -43,7 +43,7 @@ export default async function RecentPosts({ locale }: RecentPostsProps) {
|
||||
alt={post.frontmatter.title}
|
||||
fill
|
||||
className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
|
||||
unoptimized
|
||||
sizes="(max-width: 768px) 100vw, 33vw"
|
||||
/>
|
||||
<div className="absolute inset-0 image-overlay-gradient opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
{post.frontmatter.category && (
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
services:
|
||||
klz-app:
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
ports:
|
||||
- "3000:3000"
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.klz-cables-local.entrypoints=web"
|
||||
- "traefik.http.routers.klz-cables-local.rule=Host(`klz.localhost`)"
|
||||
- "traefik.http.routers.klz-cables-local.tls=false"
|
||||
- "traefik.http.routers.klz-cables-local.service=klz-cables-local"
|
||||
- "traefik.http.services.klz-cables-local.loadbalancer.server.port=3000"
|
||||
- "traefik.docker.network=infra"
|
||||
|
||||
directus:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.klz-cables-directus-local.entrypoints=web"
|
||||
- "traefik.http.routers.klz-cables-directus-local.rule=Host(`cms.klz.localhost`)"
|
||||
- "traefik.http.routers.klz-cables-directus-local.tls=false"
|
||||
- "traefik.http.routers.klz-cables-directus-local.service=klz-cables-directus-local"
|
||||
- "traefik.http.services.klz-cables-directus-local.loadbalancer.server.port=8055"
|
||||
- "traefik.docker.network=infra"
|
||||
ports:
|
||||
- "${DIRECTUS_PORT:-8055}:8055"
|
||||
|
||||
gatekeeper:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.klz-cables-gatekeeper-local.entrypoints=web"
|
||||
- "traefik.http.routers.klz-cables-gatekeeper-local.rule=Host(`gatekeeper.klz.localhost`)"
|
||||
- "traefik.http.routers.klz-cables-gatekeeper-local.tls=false"
|
||||
- "traefik.http.routers.klz-cables-gatekeeper-local.service=klz-cables-gatekeeper-local"
|
||||
- "traefik.http.services.klz-cables-gatekeeper-local.loadbalancer.server.port=3000"
|
||||
- "traefik.docker.network=infra"
|
||||
@@ -41,8 +41,9 @@ services:
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-klz-cables}-forward.headers.customrequestheaders.X-Forwarded-Ssl=on"
|
||||
|
||||
# Authentication Middleware (ForwardAuth)
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-klz-cables}-auth.forwardauth.address=http://${PROJECT_NAME:-klz-cables}-gatekeeper:3000/api/verify"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-klz-cables}-auth.forwardauth.address=http://${PROJECT_NAME:-klz-cables}-gatekeeper:3000/gatekeeper/api/verify"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-klz-cables}-auth.forwardauth.trustForwardHeader=true"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-klz-cables}-auth.forwardauth.authRequestHeaders=X-Forwarded-Host,X-Forwarded-Proto,X-Forwarded-For,Cookie"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-klz-cables}-auth.forwardauth.authResponseHeaders=X-Auth-User"
|
||||
|
||||
# Middleware Definitions
|
||||
@@ -56,7 +57,8 @@ services:
|
||||
start_period: 30s
|
||||
|
||||
gatekeeper:
|
||||
image: registry.infra.mintel.me/mintel/gatekeeper:v1.7.10
|
||||
profiles: [ "gatekeeper" ]
|
||||
image: registry.infra.mintel.me/mintel/gatekeeper:v1.7.12
|
||||
restart: always
|
||||
networks:
|
||||
infra:
|
||||
@@ -71,10 +73,11 @@ services:
|
||||
COOKIE_DOMAIN: ${COOKIE_DOMAIN}
|
||||
AUTH_COOKIE_NAME: ${AUTH_COOKIE_NAME:-klz_gatekeeper_session}
|
||||
GATEKEEPER_PASSWORD: ${GATEKEEPER_PASSWORD}
|
||||
NEXT_PUBLIC_BASE_URL: https://gatekeeper.${TRAEFIK_HOST:-testing.klz-cables.com}
|
||||
NEXT_PUBLIC_BASE_URL: ${GATEKEEPER_ORIGIN}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-klz-cables}-gatekeeper.rule=(Host(`${TRAEFIK_HOST:-testing.klz-cables.com}`) && PathPrefix(`/gatekeeper`)) || Host(`gatekeeper.${TRAEFIK_HOST:-testing.klz-cables.com}`)"
|
||||
- "traefik.docker.network=infra"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-klz-cables}-gatekeeper.rule=(Host(`${TRAEFIK_HOST:-testing.klz-cables.com}`) && PathPrefix(`/gatekeeper`))"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-klz-cables}-gatekeeper.entrypoints=websecure"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-klz-cables}-gatekeeper.tls.certresolver=le"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-klz-cables}-gatekeeper.tls=true"
|
||||
|
||||
10
package.json
10
package.json
@@ -4,9 +4,9 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@directus/sdk": "^21.0.0",
|
||||
"@mintel/mail": "^1.7.10",
|
||||
"@mintel/next-config": "^1.7.10",
|
||||
"@mintel/next-feedback": "^1.7.10",
|
||||
"@mintel/mail": "1.7.12",
|
||||
"@mintel/next-config": "1.7.12",
|
||||
"@mintel/next-feedback": "1.7.12",
|
||||
"@mintel/next-utils": "^1.7.15",
|
||||
"@react-email/components": "^1.0.7",
|
||||
"@react-pdf/renderer": "^4.3.2",
|
||||
@@ -45,8 +45,8 @@
|
||||
"@commitlint/cli": "^20.4.0",
|
||||
"@commitlint/config-conventional": "^20.4.0",
|
||||
"@lhci/cli": "^0.15.1",
|
||||
"@mintel/eslint-config": "^1.7.10",
|
||||
"@mintel/tsconfig": "^1.7.10",
|
||||
"@mintel/eslint-config": "1.7.12",
|
||||
"@mintel/tsconfig": "1.7.12",
|
||||
"@tailwindcss/cli": "^4.1.18",
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
|
||||
50
pnpm-lock.yaml
generated
50
pnpm-lock.yaml
generated
@@ -15,14 +15,14 @@ importers:
|
||||
specifier: ^21.0.0
|
||||
version: 21.1.0
|
||||
'@mintel/mail':
|
||||
specifier: ^1.7.10
|
||||
version: 1.7.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: 1.7.12
|
||||
version: 1.7.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mintel/next-config':
|
||||
specifier: ^1.7.10
|
||||
version: 1.7.10(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)(webpack@5.105.0)
|
||||
specifier: 1.7.12
|
||||
version: 1.7.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)(webpack@5.105.0)
|
||||
'@mintel/next-feedback':
|
||||
specifier: ^1.7.10
|
||||
version: 1.7.10(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
|
||||
specifier: 1.7.12
|
||||
version: 1.7.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
|
||||
'@mintel/next-utils':
|
||||
specifier: ^1.7.15
|
||||
version: 1.7.15(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)
|
||||
@@ -133,11 +133,11 @@ importers:
|
||||
specifier: ^0.15.1
|
||||
version: 0.15.1
|
||||
'@mintel/eslint-config':
|
||||
specifier: ^1.7.10
|
||||
version: 1.7.10(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
specifier: 1.7.12
|
||||
version: 1.7.12(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||
'@mintel/tsconfig':
|
||||
specifier: ^1.7.10
|
||||
version: 1.7.10
|
||||
specifier: 1.7.12
|
||||
version: 1.7.12
|
||||
'@tailwindcss/cli':
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18
|
||||
@@ -1028,20 +1028,20 @@ packages:
|
||||
'@types/react': '>=16'
|
||||
react: '>=16'
|
||||
|
||||
'@mintel/eslint-config@1.7.10':
|
||||
resolution: {integrity: sha512-77M3mi2BmjXf0hopQEL6jXsNC+x06McrqYMdNFbwDSLGg9fb7ebFFp+BWWn7zL8PlO2QO1DEEWMDm+eVasfTYQ==}
|
||||
'@mintel/eslint-config@1.7.12':
|
||||
resolution: {integrity: sha512-ofX68JWCW8ztD9tt/1MDb6pSr9MJKq3js3Vny7VoT/bObjpR/iO9tJp0ekiq12Ps8VTEgDh1qwwmf2wrJJBRpQ==}
|
||||
|
||||
'@mintel/mail@1.7.10':
|
||||
resolution: {integrity: sha512-ISfXEob8eVEoh8Lnsa2LlZ/42iJT3OXbZF4i8IvEXByFVEDe2OFEDUxtNMCgEz050OXTb0EdIGbR7AOiB7Q7kg==}
|
||||
'@mintel/mail@1.7.12':
|
||||
resolution: {integrity: sha512-2MqGSDhXQ6jaswUs/s74pz2LwmOhtOTWltGgwb8JF2JdfQt8FE5H4XZsvSV12Q1Fs1n5+V09OZTWU1TyFmh6lw==}
|
||||
peerDependencies:
|
||||
react: ^19.0.0
|
||||
react-dom: ^19.0.0
|
||||
|
||||
'@mintel/next-config@1.7.10':
|
||||
resolution: {integrity: sha512-ip+7CTjiU5YwoezBkgfPRZLwOcVB4VQs2xFZYmwNDGPQgu1q/1dDD+TiU7PVVRL9PsxHgghzpBZstSEDoubNEA==}
|
||||
'@mintel/next-config@1.7.12':
|
||||
resolution: {integrity: sha512-GBFIgF2vRzPl03B2RwaEyBpwjXgDRHUtaal4QQ2n7TW4G1lNIKLTBLdsJtmxMn8VlUbzRVMaFNm7m6joSNiJ0w==}
|
||||
|
||||
'@mintel/next-feedback@1.7.10':
|
||||
resolution: {integrity: sha512-WgENMSWp9AcWZ4lIN8p7xSp8UiABbSyY5Hch9XUg+ZPBxeClWGjl6typkqjhz7YsMAgj0DYKlLtri9cYy71suQ==}
|
||||
'@mintel/next-feedback@1.7.12':
|
||||
resolution: {integrity: sha512-nlaeV+IRmqwzaAZFTmj8+RvyMsvh+SNs0BopWgbDdAt2x1yoz4fC6cpn+v7KjfnVW0YWPZhMeGD/uEgMhVrXRA==}
|
||||
peerDependencies:
|
||||
react: ^19.0.0
|
||||
react-dom: ^19.0.0
|
||||
@@ -1049,8 +1049,8 @@ packages:
|
||||
'@mintel/next-utils@1.7.15':
|
||||
resolution: {integrity: sha512-CqSe3eHamq9zLs+AJxGOPypTLchw/oZ3JcLkor007PcUDMTv/Lspfv5oCaXK2s0FeIOJaa2QwSGPDI1h5/3ZVw==}
|
||||
|
||||
'@mintel/tsconfig@1.7.10':
|
||||
resolution: {integrity: sha512-3PtaPA0UYitv21ND3/BGAn9WCqfrOjllbr4/Cb+XCTJFQYp2sacBfoSTzE/4RV9TnEIBpAZlgINT1WrvIvN6pQ==}
|
||||
'@mintel/tsconfig@1.7.12':
|
||||
resolution: {integrity: sha512-WGs/p2E1xQGkzNasLCZKoplKIhxC17NZRhBYH5O43lp98aHOZMC3BKgNeLYUfoEFGiIN1hx2FUJ69DosQc0xmw==}
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||
@@ -7473,7 +7473,7 @@ snapshots:
|
||||
'@types/react': 19.2.13
|
||||
react: 19.2.4
|
||||
|
||||
'@mintel/eslint-config@1.7.10(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
'@mintel/eslint-config@1.7.12(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint/eslintrc': 3.3.3
|
||||
'@eslint/js': 9.39.2
|
||||
@@ -7490,13 +7490,13 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@mintel/mail@1.7.10(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@mintel/mail@1.7.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@react-email/components': 0.0.33(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@mintel/next-config@1.7.10(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)(webpack@5.105.0)':
|
||||
'@mintel/next-config@1.7.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@swc/helpers@0.5.18)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)(typescript@5.9.3)(webpack@5.105.0)':
|
||||
dependencies:
|
||||
'@sentry/nextjs': 10.38.0(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4)(webpack@5.105.0)
|
||||
next: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
|
||||
@@ -7519,7 +7519,7 @@ snapshots:
|
||||
- typescript
|
||||
- webpack
|
||||
|
||||
'@mintel/next-feedback@1.7.10(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)':
|
||||
'@mintel/next-feedback@1.7.12(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)':
|
||||
dependencies:
|
||||
'@directus/sdk': 21.1.0
|
||||
clsx: 2.1.1
|
||||
@@ -7557,7 +7557,7 @@ snapshots:
|
||||
- sass
|
||||
- typescript
|
||||
|
||||
'@mintel/tsconfig@1.7.10': {}
|
||||
'@mintel/tsconfig@1.7.12': {}
|
||||
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
dependencies:
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
@theme {
|
||||
--font-sans:
|
||||
'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
||||
Arial, sans-serif;
|
||||
var(--font-inter), system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', Arial, sans-serif;
|
||||
--font-heading: 'Inter', system-ui, sans-serif;
|
||||
--font-body: 'Inter', system-ui, sans-serif;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user