Files
e-tib.com/components/blocks/CertificatesBlock.tsx
Marc Mintel 0cc7d0805e
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 19s
Build & Deploy / 🧪 QA (push) Successful in 1m23s
Build & Deploy / 🏗️ Build (push) Successful in 2m46s
Build & Deploy / 🚀 Deploy (push) Successful in 25s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 52s
Build & Deploy / 🔔 Notify (push) Successful in 2s
Nightly QA / 💨 Smoke & Health (push) Failing after 1m6s
fix(content): correct ISO certificate validity dates from OCR-verified scans
2026-09-08 11:56:49 +02:00

234 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import { m } from 'framer-motion';
import { Container } from '@/components/ui/Container';
import { Button } from '@/components/ui/Button';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
import { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';
import { FileText, Award, Download, ShieldCheck } from 'lucide-react';
import { LogoArcs } from '@/components/ui/LogoArcs';
interface Certificate {
title: string;
description: string;
pdfUrl?: string;
type: 'iso' | 'tax' | 'general';
date?: string;
}
interface CertificatesBlockProps {
badge?: string;
title?: string;
description?: string;
certificates?: Certificate[];
hideHeader?: boolean;
}
export const defaultCertificates: Certificate[] = [
{
title: 'ISO 14001:2015',
description: 'Umweltmanagementsystem',
pdfUrl: '/assets/certificates/iso14001.pdf',
type: 'iso',
date: '06.12.2023 05.12.2026',
},
{
title: 'ISO 9001:2015',
description: 'Qualitätsmanagementsystem',
pdfUrl: '/assets/certificates/iso9001.pdf',
type: 'iso',
date: '06.12.2023 05.12.2026',
},
{
title: 'DIN EN ISO 45001:2018',
description: 'Arbeits- und Gesundheitsschutz',
pdfUrl: '/assets/certificates/iso45001.pdf',
type: 'iso',
date: '06.12.2025 05.12.2028',
},
{
title: 'Betriebshaftpflichtversicherung',
description: 'RheinLand Versicherungen (10 Mio. € Deckung)',
pdfUrl: '/assets/certificates/betriebshaftpflicht.pdf',
type: 'general',
date: '25.03.2026',
},
{
title: 'Freistellungsbescheinigung',
description: 'Nach § 48 b EStG',
pdfUrl: '/assets/certificates/freistellung.pdf',
type: 'tax',
date: '09.02.2024',
},
{
title: 'Nachweis § 13b UStG',
description: 'Steuerschuldnerschaft des Leistungsempfängers',
pdfUrl: '/assets/certificates/nachweis13b.pdf',
type: 'tax',
date: '09.02.2024',
},
{
title: 'Bescheinigung in Steuersachen',
description: 'Zertifikat des Finanzamtes',
pdfUrl: '/assets/certificates/bescheinigung.pdf',
type: 'tax',
date: '13.02.2025',
},
];
export function CertificatesBlock({ badge, title, description, certificates = defaultCertificates, hideHeader = false }: CertificatesBlockProps) {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
const badgeText = badge || 'Zertifizierungen & Nachweise';
const titleText = title || 'Geprüfte Qualität und Sicherheit';
const descriptionText = description || 'Transparenz und höchste Standards sind das Fundament unserer Arbeit. Hier finden Sie unsere aktuellen Zertifikate und Bescheinigungen als PDF-Download.';
const containerVariants = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0, transition: { duration: 1.0, ease: [0.16, 1, 0.3, 1] } },
};
if (!isMounted) {
return (
<section className="py-16 md:py-24 lg:py-32 bg-neutral-50 relative overflow-hidden">
<Container>
{!hideHeader && (
<div className="text-center max-w-3xl mx-auto mb-16">
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badgeText}</h2>
<h3 className="font-heading text-2xl md:text-5xl font-extrabold text-neutral-dark mb-6">{titleText}</h3>
<p className="text-lg text-neutral-600">{descriptionText}</p>
</div>
)}
</Container>
</section>
);
}
return (
<section className="py-12 md:py-24 bg-neutral-50 relative overflow-hidden">
{/* Background Accents */}
<div className="absolute top-0 right-0 w-1/3 h-full bg-primary/5 -skew-x-12 translate-x-1/2" />
<div className="absolute bottom-0 left-0 w-1/4 h-1/2 bg-accent/5 skew-y-12 -translate-x-1/2 blur-3xl rounded-full" />
<Container className="relative z-10">
{!hideHeader && (
<div className="text-center max-w-3xl mx-auto mb-16">
<m.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 1.0, ease: [0.16, 1, 0.3, 1] }}
>
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badgeText}</h2>
<h3 className="font-heading text-2xl md:text-5xl font-extrabold text-neutral-dark mb-6">{titleText}</h3>
<p className="text-lg text-neutral-600">{descriptionText}</p>
</m.div>
</div>
)}
<m.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
variants={containerVariants}
initial="hidden"
whileInView="show"
viewport={{ once: true, margin: "-50px" }}
>
{certificates.map((cert, index) => {
const isIso = cert.type === 'iso';
const Wrapper = cert.pdfUrl ? m.a : m.div;
const wrapperProps = cert.pdfUrl ? {
href: encodeURI(cert.pdfUrl),
target: "_blank",
rel: "noopener noreferrer",
download: cert.pdfUrl.split('/').pop()
} : {};
return (
<Wrapper
key={index}
{...wrapperProps as any}
variants={itemVariants}
className={`group relative rounded-2xl p-6 md:p-8 flex flex-col justify-between overflow-hidden transition-all duration-500 hover:shadow-xl hover:-translate-y-1 border ${
isIso
? 'bg-neutral-dark text-white border-neutral-800 shadow-lg'
: 'bg-white text-neutral-dark border-neutral-200 shadow-md'
}`}
>
{/* Background Decorative Elements */}
<div className={`absolute top-0 right-0 w-32 h-32 blur-3xl rounded-full -mr-10 -mt-10 transition-colors duration-500 ${
isIso ? 'bg-primary/20 group-hover:bg-primary/40' : 'bg-primary/10 group-hover:bg-primary/20'
}`} />
{isIso && (
<div className="absolute -bottom-[20%] -right-[20%] w-[100%] h-[100%] z-0 opacity-10 group-hover:opacity-20 transition-opacity duration-1000 pointer-events-none mix-blend-overlay">
<LogoArcs className="w-full h-full text-white animate-[spin_60s_linear_infinite]" />
</div>
)}
<HoverShineOverlay shineColor={isIso ? 'via-white/20' : 'via-black/5'} />
<AnimatedGlossyBorder color={isIso ? 'primary' : 'primary'} className="opacity-0 group-hover:opacity-100 transition-opacity duration-700" borderWidth={2} />
<div className="relative z-10 flex flex-col h-full">
<div className="flex justify-between items-start mb-8">
<div className={`p-3 rounded-xl inline-flex ${
isIso ? 'bg-primary/20 text-primary-light backdrop-blur-md' : 'bg-primary/10 text-primary'
}`}>
{isIso ? <Award size={28} /> : <ShieldCheck size={28} />}
</div>
{cert.date && (
<span className={`text-xs font-bold uppercase tracking-wider px-3 py-1 rounded-full ${
isIso ? 'bg-neutral-800 text-neutral-400 border border-neutral-700' : 'bg-neutral-100 text-neutral-500 border border-neutral-200'
}`}>
{cert.date}
</span>
)}
</div>
<div>
<h4 className={`text-xl font-bold mb-2 ${isIso ? 'text-white' : 'text-neutral-dark'}`}>
{cert.title}
</h4>
<p className={`text-sm mb-6 ${isIso ? 'text-neutral-400' : 'text-neutral-600'}`}>
{cert.description}
</p>
</div>
<div className={`mt-auto inline-flex items-center gap-2 text-sm font-bold uppercase tracking-wider transition-all duration-300 ${
cert.pdfUrl ? 'group-hover:gap-3' : ''
} ${
isIso ? 'text-primary-light' : 'text-primary'
}`}>
{cert.pdfUrl ? (
<>
<span>Download PDF</span>
<Download size={16} className="group-hover:translate-y-0.5 transition-transform" />
</>
) : (
<span className="text-neutral-400">Nachweis liegt vor</span>
)}
</div>
</div>
</Wrapper>
);
})}
</m.div>
</Container>
</section>
);
}