Files
e-tib.com/components/blocks/JobListingBlock.tsx
Marc Mintel 474672b2a1
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🚀 Deploy (push) Successful in 34s
Build & Deploy / 🧪 QA (push) Successful in 1m46s
Build & Deploy / 🏗️ Build (push) Successful in 3m38s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m19s
Build & Deploy / 🔔 Notify (push) Successful in 5s
chore: release 2.2.69 (PageSpeed optimizations)
2026-06-26 09:53:12 +02:00

240 lines
12 KiB
TypeScript

'use client';
import React from 'react';
import Link from 'next/link';
import { Button } from '@/components/ui/Button';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
import { m } from 'framer-motion';
import { useTranslations } from 'next-intl';
export interface JobListingBlockProps {
title?: string;
showJobs?: boolean;
showFairs?: boolean;
fairsTitle?: string;
fairs?: Array<{
name: string;
date: string;
type: string;
location?: string;
booth?: string;
url?: string;
}>;
emptyStateMessage?: string;
emptyStateLinkText?: string;
emptyStateLinkHref?: string;
}
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.1,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 1.0, ease: [0.16, 1, 0.3, 1] as const },
},
};
export const JobListingBlock = (props: JobListingBlockProps) => {
const t = useTranslations('JobListingBlock');
const {
showJobs = true,
showFairs,
fairsTitle = t('fairsTitle'),
fairs = [
{ name: 'Intersolar München', date: 'Termin folgt', type: 'Energie-Messe', location: 'Messe München' },
{ name: 'Windenergietage Linstow', date: 'Termin folgt', type: 'Fachkongress', location: 'Linstow' },
{ name: 'Kabelwerkstatt Wiesbaden', date: 'Termin folgt', type: 'Fachmesse', location: 'Wiesbaden' }
],
emptyStateMessage = t('emptyStateMessage'),
emptyStateLinkText = t('emptyStateLinkText'),
emptyStateLinkHref = t('emptyStateLinkHref')
} = props;
const title = props.title || t('title');
// Since Payload is removed, we use an empty array or static data
const jobs: any[] = [];
return (
<div className="py-12 md:py-24">
<div className="container mx-auto">
{showFairs && (
<div className="mb-12 md:mb-24 relative">
<m.h3
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }}
className="font-heading font-extrabold text-3xl md:text-4xl text-neutral-dark mb-8 md:mb-12 flex items-center gap-4"
>
<span className="w-12 h-1.5 bg-primary rounded-full" />
{fairsTitle}
</m.h3>
<m.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-50px" }}
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
>
{fairs.map((messe, idx) => {
const isLink = !!messe.url;
const innerContent = (
<>
{/* Glass Background Elements */}
<div className="absolute inset-0 bg-gradient-to-br from-white/[0.03] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700" />
<div className="absolute -top-32 -right-32 w-64 h-64 bg-primary/20 blur-[100px] rounded-full group-hover:bg-primary/30 transition-colors duration-700" />
<HoverShineOverlay shineColor="via-primary/10" />
<div className="relative z-10 flex-grow flex flex-col">
<div className="flex justify-between items-start mb-6">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/[0.05] border border-white/10 text-xs font-bold uppercase tracking-wider text-primary-light backdrop-blur-sm">
<span className="w-1.5 h-1.5 rounded-full bg-primary animate-pulse shadow-[0_0_8px_rgba(var(--color-primary),1)]" />
{messe.type}
</div>
{isLink && (
<div className="w-8 h-8 rounded-full bg-white/[0.05] border border-white/10 flex items-center justify-center text-white/40 group-hover:text-primary transition-colors group-hover:bg-primary/10">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17l9.2-9.2M17 17V7H7"/></svg>
</div>
)}
</div>
<h4 className="font-heading font-bold text-3xl text-white mb-8 group-hover:text-primary-light transition-colors leading-tight">
{messe.name}
</h4>
<div className="mt-auto space-y-4">
<div className="flex items-start gap-3">
<div className="w-8 h-8 rounded-full bg-white/[0.05] flex items-center justify-center text-primary shrink-0 mt-0.5">
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
</div>
<div>
<p className="text-xs text-white/40 uppercase tracking-widest font-heading mb-1">{t('date')}</p>
<p className="text-white/90 font-medium">{messe.date}</p>
</div>
</div>
{messe.location && (
<div className="flex items-start gap-3">
<div className="w-8 h-8 rounded-full bg-white/[0.05] flex items-center justify-center text-primary shrink-0 mt-0.5">
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
</div>
<div>
<p className="text-xs text-white/40 uppercase tracking-widest font-heading mb-1">{t('location')}</p>
<p className="text-white/90 font-medium">{messe.location}</p>
</div>
</div>
)}
{messe.booth && (
<div className="flex items-start gap-3">
<div className="w-8 h-8 rounded-full bg-white/[0.05] flex items-center justify-center text-primary shrink-0 mt-0.5">
<svg xmlns="http://www.w3.org/2000/svg" className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
</div>
<div>
<p className="text-xs text-white/40 uppercase tracking-widest font-heading mb-1">{t('booth')}</p>
<p className="text-primary-light font-bold text-sm">{messe.booth}</p>
</div>
</div>
)}
</div>
</div>
</>
);
const baseClasses = "group relative bg-[#050B14] rounded-[2rem] p-8 overflow-hidden border border-white/5 shadow-2xl transition-all duration-500 hover:scale-[1.02] hover:shadow-primary/10 flex flex-col h-full";
if (isLink) {
return (
<m.a variants={itemVariants} key={`fair-${idx}`} href={messe.url} target="_blank" rel="noopener noreferrer" className={`${baseClasses} cursor-pointer`}>
{innerContent}
</m.a>
);
}
return (
<m.div variants={itemVariants} key={`fair-${idx}`} className={baseClasses}>
{innerContent}
</m.div>
);
})}
</m.div>
</div>
)}
{showJobs && (
<div className="mb-12">
<m.h2
initial={{ opacity: 0, x: -20 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }}
className="font-heading font-extrabold text-3xl md:text-4xl text-neutral-dark mb-6 md:mb-8"
>
{title}
</m.h2>
{jobs.length > 0 ? (
<m.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-50px" }}
className="grid gap-6"
>
{jobs.map((job: any) => (
<m.div variants={itemVariants} key={job.id}>
<Link
href={`/karriere/${job.slug}`}
className="group bg-white border border-neutral-100 p-8 rounded-2xl shadow-sm hover:shadow-xl hover:border-primary/20 transition-all flex flex-col md:flex-row md:items-center justify-between gap-6 block"
>
<div>
<div className="flex items-center gap-3 mb-2">
<span className="px-3 py-1 bg-primary/10 text-primary text-xs font-bold rounded-full uppercase">{job.type}</span>
<span className="text-text-secondary text-sm font-medium">{job.department}</span>
</div>
<h4 className="font-heading font-bold text-2xl text-neutral-dark group-hover:text-primary transition-colors">{job.title}</h4>
<p className="text-text-secondary mt-1">{job.location}</p>
</div>
<div className="flex items-center text-primary font-bold group-hover:translate-x-2 transition-transform">
{t('viewDetails')}
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 ml-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</div>
</Link>
</m.div>
))}
</m.div>
) : (
<m.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] as const }}
className="bg-neutral-50 border border-neutral-100 rounded-2xl p-12 text-center"
>
<p className="text-text-secondary text-lg">{emptyStateMessage}</p>
<Button href={emptyStateLinkHref} variant="primary" className="mt-6">
{emptyStateLinkText}
</Button>
</m.div>
)}
</div>
)}
</div>
</div>
);
};