feat: integrate mintel annotator with email submission and environment restrictions
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 38s
Build & Deploy / 🧪 QA (push) Failing after 36s
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 3s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 38s
Build & Deploy / 🧪 QA (push) Failing after 36s
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 3s
This commit is contained in:
@@ -10,6 +10,7 @@ import { getMessages } from 'next-intl/server';
|
||||
import '../../styles/globals.css';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import FeedbackClientWrapper from '@/components/FeedbackClientWrapper';
|
||||
import AnnotatorClientWrapper from '@/components/AnnotatorClientWrapper';
|
||||
import { setRequestLocale } from 'next-intl/server';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { mapFileSlugToTranslated } from '@/lib/slugs';
|
||||
@@ -214,6 +215,7 @@ export default async function Layout(props: {
|
||||
<Footer companyInfo={companyInfo} />
|
||||
<JsonLd />
|
||||
<AnalyticsShell />
|
||||
{process.env.TARGET !== 'production' && <AnnotatorClientWrapper />}
|
||||
{feedbackEnabled && <FeedbackClientWrapper feedbackEnabled={feedbackEnabled} />}
|
||||
</TransitionProvider>
|
||||
</NextIntlClientProvider>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import ReferenceDetail from './page';
|
||||
|
||||
// Mock next/navigation
|
||||
vi.mock('next/navigation', () => ({
|
||||
notFound: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock next-intl/server
|
||||
vi.mock('next-intl/server', () => ({
|
||||
setRequestLocale: vi.fn(),
|
||||
getTranslations: vi.fn().mockResolvedValue(() => 'translated text'),
|
||||
}));
|
||||
|
||||
// Mock @/lib/references
|
||||
vi.mock('@/lib/references', () => ({
|
||||
getReferenceBySlug: vi.fn().mockResolvedValue({
|
||||
slug: 'test-slug',
|
||||
frontmatter: {
|
||||
title: 'Test Title',
|
||||
location: 'Test Location',
|
||||
category: 'Test Category',
|
||||
},
|
||||
content: 'Test content',
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ReferenceDetail Page TDD', () => {
|
||||
it('renders reference detail correctly', async () => {
|
||||
const props = {
|
||||
params: Promise.resolve({ locale: 'de', slug: 'test-slug' })
|
||||
};
|
||||
|
||||
// We expect the execution to fail if getTranslations is not defined/imported inside page.tsx
|
||||
const component = await ReferenceDetail(props);
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,212 +0,0 @@
|
||||
import { notFound } from 'next/navigation';
|
||||
import { Container, Badge, Heading } from '@/components/ui';
|
||||
import { setRequestLocale, getTranslations } from 'next-intl/server';
|
||||
import { Metadata } from 'next';
|
||||
import { getReferenceBySlug, getAllReferences } from '@/lib/references';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import TrackedLink from '@/components/analytics/TrackedLink';
|
||||
import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||
import { MapPin, Calendar, Briefcase, ChevronLeft } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
locale: string;
|
||||
slug: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
const { locale, slug } = await params;
|
||||
if (locale !== 'de' && locale !== 'en') return {};
|
||||
|
||||
const reference = await getReferenceBySlug(slug, locale);
|
||||
if (!reference) return {};
|
||||
|
||||
return {
|
||||
title: `${reference.frontmatter.title} | Referenzen | E-TIB Gruppe`,
|
||||
description: `Details zum Projekt ${reference.frontmatter.title} in ${reference.frontmatter.location}.`,
|
||||
alternates: {
|
||||
canonical: `${SITE_URL}/${locale}/referenzen/${slug}`,
|
||||
},
|
||||
openGraph: {
|
||||
title: `${reference.frontmatter.title} | E-TIB`,
|
||||
description: `Details zum Projekt ${reference.frontmatter.title} in ${reference.frontmatter.location}.`,
|
||||
url: `${SITE_URL}/${locale}/referenzen/${slug}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateStaticParams(props: any) {
|
||||
const params = await props.params;
|
||||
const locale = params?.locale || 'de'; // fallback to 'de' if undefined
|
||||
const references = await getAllReferences(locale);
|
||||
return references.map((ref) => ({
|
||||
slug: ref.slug,
|
||||
locale
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function ReferenceDetail(props: { params: Promise<{ locale: string; slug: string }> }) {
|
||||
const { locale, slug } = await props.params;
|
||||
|
||||
if (locale !== 'de' && locale !== 'en') {
|
||||
notFound();
|
||||
}
|
||||
|
||||
setRequestLocale(locale);
|
||||
const reference = await getReferenceBySlug(slug, locale);
|
||||
const t = await getTranslations('ReferenceDetail');
|
||||
|
||||
if (!reference) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// MDX components mapping for industrial typography
|
||||
const mdxComponents = {
|
||||
Heading,
|
||||
h1: (props: any) => <Heading level={1} size="2" className="hidden" {...props} />, // Hidden because Hero handles H1
|
||||
h2: (props: any) => <Heading level={2} size="3" className="mt-16 mb-6 border-b border-neutral-100 pb-4 text-neutral-dark" {...props} />,
|
||||
h3: (props: any) => <Heading level={3} size="4" className="mt-12 mb-4 text-primary" {...props} />,
|
||||
h4: (props: any) => <Heading level={4} size="5" className="mt-8 mb-4 uppercase tracking-widest text-neutral-500" {...props} />,
|
||||
p: (props: any) => <p className="text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl" {...props} />,
|
||||
ul: (props: any) => <ul className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6 my-12" {...props} />,
|
||||
ol: (props: any) => <ol className="list-decimal pl-6 mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl" {...props} />,
|
||||
li: (props: any) => {
|
||||
// Smart Metric Card Logic
|
||||
let text = '';
|
||||
if (typeof props.children === 'string') {
|
||||
text = props.children;
|
||||
} else if (Array.isArray(props.children)) {
|
||||
text = props.children.map((c: any) => typeof c === 'string' ? c : '').join('');
|
||||
}
|
||||
|
||||
const match = text.match(/^([\d.,]+)\s*(m|Satz|Stück|km|kV|Fasern)\b(.*)/);
|
||||
|
||||
if (match && typeof props.children === 'string') {
|
||||
const [, value, unit, rest] = match;
|
||||
return (
|
||||
<li className="bg-white p-6 md:p-8 rounded-3xl border border-neutral-100 flex flex-col justify-center hover:shadow-[0_20px_40px_-15px_rgba(0,0,0,0.05)] hover:border-primary/30 transition-all duration-500 group hover:-translate-y-1 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-neutral-50 to-transparent rounded-bl-full -z-10 group-hover:from-primary/5 transition-colors duration-500" />
|
||||
<div className="flex items-baseline gap-2 mb-3">
|
||||
<span className="text-4xl md:text-5xl font-bold font-heading text-primary tracking-tight">{value}</span>
|
||||
<span className="text-lg md:text-xl font-bold text-neutral-400">{unit}</span>
|
||||
</div>
|
||||
<span className="text-neutral-600 font-semibold text-sm md:text-base leading-snug">{rest.replace(/^[\s-]*\s/, '')}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="bg-neutral-50 p-6 rounded-3xl border border-neutral-100 flex items-start gap-4 hover:shadow-md transition-all duration-300 group hover:-translate-y-1">
|
||||
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center shrink-0 mt-0.5 group-hover:bg-primary/20 transition-colors">
|
||||
<svg className="w-4 h-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-neutral-700 font-semibold leading-relaxed">{props.children}</span>
|
||||
</li>
|
||||
);
|
||||
},
|
||||
a: (props: any) => <a className="text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold" {...props} />,
|
||||
strong: (props: any) => <strong className="font-bold text-neutral-900" {...props} />,
|
||||
blockquote: (props: any) => (
|
||||
<blockquote className="border-l-4 border-primary pl-6 py-3 my-10 italic bg-neutral-50 rounded-r-2xl text-neutral-700 font-medium max-w-3xl shadow-sm" {...props} />
|
||||
),
|
||||
hr: (props: any) => <hr className="my-16 border-t-2 border-neutral-100 max-w-3xl" {...props} />,
|
||||
img: (props: any) => <img className="rounded-2xl shadow-2xl my-12 max-w-full h-auto border border-neutral-100" {...props} />,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-white">
|
||||
{/* Hero Section */}
|
||||
<section className="bg-[#050B14] text-white pt-28 pb-10 md:pt-52 md:pb-24 min-h-[30vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden">
|
||||
{reference.frontmatter.featuredImage && (
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src={reference.frontmatter.featuredImage}
|
||||
alt={reference.frontmatter.title}
|
||||
fill
|
||||
className="object-cover opacity-30 mix-blend-luminosity scale-105"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-[#050B14] via-[#050B14]/70 to-transparent" />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-[#050B14] via-[#050B14]/40 to-transparent" />
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 opacity-20 z-0 pointer-events-none">
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-primary/30 via-transparent to-transparent" />
|
||||
</div>
|
||||
<Container className="relative z-10">
|
||||
<div className="mb-12">
|
||||
<TrackedLink
|
||||
href={`/${locale}/referenzen`}
|
||||
className="inline-flex items-center gap-2 text-white/60 hover:text-white transition-colors uppercase tracking-widest text-xs font-bold"
|
||||
eventProperties={{ location: 'reference_back_btn' }}
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
{t('backToOverview')}
|
||||
</TrackedLink>
|
||||
</div>
|
||||
<div className="max-w-5xl">
|
||||
<Badge variant="accent" className="mb-6">
|
||||
{t('projectReference')}
|
||||
</Badge>
|
||||
<Heading level={1} variant="white" className="mb-10 leading-[1.1] md:text-6xl lg:text-7xl drop-shadow-lg">
|
||||
{reference.frontmatter.title}
|
||||
</Heading>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 md:gap-6 mt-12 md:mt-16">
|
||||
<div className="flex items-start gap-4 p-6 md:p-8 bg-white/5 rounded-3xl border border-white/10 backdrop-blur-md transition-all hover:bg-white/10">
|
||||
<MapPin className="w-8 h-8 text-primary shrink-0 opacity-80" />
|
||||
<div>
|
||||
<p className="text-xs text-white/50 mb-1.5 uppercase tracking-widest font-bold">{t('location')}</p>
|
||||
<p className="font-semibold text-lg md:text-xl text-white/90">{reference.frontmatter.location}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4 p-6 md:p-8 bg-white/5 rounded-3xl border border-white/10 backdrop-blur-md transition-all hover:bg-white/10">
|
||||
<Briefcase className="w-8 h-8 text-primary shrink-0 opacity-80" />
|
||||
<div>
|
||||
<p className="text-xs text-white/50 mb-1.5 uppercase tracking-widest font-bold">{t('client')}</p>
|
||||
<p className="font-semibold text-lg md:text-xl text-white/90 line-clamp-2">{reference.frontmatter.client}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4 p-6 md:p-8 bg-white/5 rounded-3xl border border-white/10 backdrop-blur-md transition-all hover:bg-white/10">
|
||||
<Calendar className="w-8 h-8 text-primary shrink-0 opacity-80" />
|
||||
<div>
|
||||
<p className="text-xs text-white/50 mb-1.5 uppercase tracking-widest font-bold">{t('period')}</p>
|
||||
<p className="font-semibold text-lg md:text-xl text-white/90">{reference.frontmatter.dateString || new Date(reference.frontmatter.date).getFullYear()}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<Container className="py-16 md:py-24">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h2 className="text-3xl font-bold text-neutral-dark mb-8">{t('scopeTitle')}</h2>
|
||||
|
||||
<div className="w-full">
|
||||
<MDXRemote source={reference.content} components={mdxComponents} />
|
||||
</div>
|
||||
|
||||
<div className="mt-20 flex justify-center">
|
||||
<TrackedLink
|
||||
href={`/${locale}/referenzen`}
|
||||
className={getButtonClasses('primary', 'lg')}
|
||||
eventProperties={{ location: 'reference_bottom_back_btn' }}
|
||||
>
|
||||
<span className="relative z-10 flex items-center justify-center gap-2">
|
||||
{t('viewAll')}
|
||||
</span>
|
||||
<ButtonOverlay variant="primary" />
|
||||
</TrackedLink>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -90,11 +90,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}`}
|
||||
eventProperties={{ location: 'reference_list_card', 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-pointer block"
|
||||
className="flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm block"
|
||||
>
|
||||
<div className="flex flex-col h-full relative">
|
||||
{/* Image Section */}
|
||||
@@ -111,7 +109,7 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
<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 transition-colors duration-500" />
|
||||
<div className="absolute inset-0 bg-black/5" />
|
||||
|
||||
{/* Location Badge */}
|
||||
<div className="absolute top-4 left-4 z-10">
|
||||
@@ -124,7 +122,7 @@ 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 transition-colors line-clamp-3 leading-[1.2] group-hover:text-primary">
|
||||
<h3 className="text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 leading-[1.2]">
|
||||
{ref.frontmatter.title}
|
||||
</h3>
|
||||
|
||||
@@ -147,7 +145,7 @@ export default async function ReferenzenOverview(props: { params: Promise<{ loca
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TrackedLink>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
35
app/actions/getAnnotatorAssets.ts
Normal file
35
app/actions/getAnnotatorAssets.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
'use server';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export async function getAnnotatorAssets() {
|
||||
// Try /app/public first (docker), then process.cwd()/public
|
||||
let publicDir = '/app/public';
|
||||
if (!fs.existsSync(publicDir)) {
|
||||
publicDir = path.join(process.cwd(), 'public');
|
||||
}
|
||||
|
||||
const walk = (dir: string): string[] => {
|
||||
let results: string[] = [];
|
||||
if (!fs.existsSync(dir)) return results;
|
||||
const list = fs.readdirSync(dir);
|
||||
list.forEach((file) => {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat && stat.isDirectory()) {
|
||||
results = results.concat(walk(filePath));
|
||||
} else {
|
||||
if (filePath.match(/\.(png|jpg|jpeg|webp|mp4|svg)$/i)) {
|
||||
results.push(filePath.replace(publicDir, ''));
|
||||
}
|
||||
}
|
||||
});
|
||||
return results;
|
||||
};
|
||||
const results = walk(publicDir);
|
||||
console.log('[Annotator] publicDir:', publicDir);
|
||||
console.log('[Annotator] Assets found:', results.length);
|
||||
console.log('[Annotator] First 5 paths:', results.slice(0, 5));
|
||||
|
||||
return results;
|
||||
}
|
||||
54
app/actions/submitAnnotations.ts
Normal file
54
app/actions/submitAnnotations.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { sendEmail } from '@/lib/mail/mailer';
|
||||
import { getServerAppServices } from '@/lib/services/create-services.server';
|
||||
|
||||
export async function submitAnnotations(annotations: any[]) {
|
||||
const services = getServerAppServices();
|
||||
const logger = services.logger.child({ action: 'submitAnnotations' });
|
||||
|
||||
if (!annotations || annotations.length === 0) {
|
||||
return { success: false, error: 'Keine Korrekturen vorhanden.' };
|
||||
}
|
||||
|
||||
try {
|
||||
const jsonContent = JSON.stringify(annotations, null, 2);
|
||||
const htmlContent = `
|
||||
<h2>Neue Website-Korrekturen (${annotations.length})</h2>
|
||||
<p>Es wurden neue Korrekturen über den Annotator eingereicht.</p>
|
||||
<ul>
|
||||
${annotations.map(ann => `
|
||||
<li>
|
||||
<b>${new URL(ann.url).pathname}</b> [${ann.type}]<br />
|
||||
Element: <code>${ann.selector}</code><br />
|
||||
<i>${ann.type === 'image' ? 'Neues Bild: ' : 'Text: '}${ann.newContent}</i>
|
||||
</li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
<p><i>Die genauen Daten (inkl. Scroll-Positionen, Viewport und Zeitstempel) befinden sich im JSON-Anhang.</i></p>
|
||||
`;
|
||||
|
||||
const notificationResult = await sendEmail({
|
||||
replyTo: 'info@e-tib.com', // Falls Rückfragen nötig
|
||||
subject: `[Korrekturen] ${annotations.length} neue Anmerkungen für E-TIB`,
|
||||
html: htmlContent,
|
||||
attachments: [
|
||||
{
|
||||
filename: 'corrections.json',
|
||||
content: jsonContent,
|
||||
contentType: 'application/json',
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!notificationResult.success) {
|
||||
logger.error('Annotation email FAILED', { error: notificationResult.error });
|
||||
return { success: false, error: notificationResult.error };
|
||||
}
|
||||
|
||||
logger.info('Annotation email sent successfully', { count: annotations.length });
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logger.error('Failed to send annotations', { error: errorMsg });
|
||||
return { success: false, error: errorMsg };
|
||||
}
|
||||
}
|
||||
30
components/AnnotatorClientWrapper.tsx
Normal file
30
components/AnnotatorClientWrapper.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
'use client';
|
||||
|
||||
import { Annotator } from '@mintel/annotator';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getAnnotatorAssets } from '@/app/actions/getAnnotatorAssets';
|
||||
import { submitAnnotations } from '@/app/actions/submitAnnotations';
|
||||
|
||||
export default function AnnotatorClientWrapper() {
|
||||
const [assets, setAssets] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
getAnnotatorAssets().then(fetchedAssets => {
|
||||
console.log('[AnnotatorClientWrapper] fetchedAssets:', fetchedAssets);
|
||||
if (Array.isArray(fetchedAssets)) {
|
||||
setAssets(fetchedAssets);
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('[AnnotatorClientWrapper] Action failed:', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (annotations: any[]) => {
|
||||
const result = await submitAnnotations(annotations);
|
||||
if (!result.success) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
};
|
||||
|
||||
return <Annotator assets={assets} onSubmit={handleSubmit} />;
|
||||
}
|
||||
@@ -131,16 +131,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}`}
|
||||
<div
|
||||
data-testid="reference-tile"
|
||||
draggable={false}
|
||||
onClick={(e) => {
|
||||
if (dragDistanceRef.current > 15) {
|
||||
e.preventDefault();
|
||||
}
|
||||
dragDistanceRef.current = 0;
|
||||
}}
|
||||
className="block relative select-none aspect-[16/10] bg-neutral-800 rounded-2xl overflow-hidden mb-5 border border-white/5 shadow-2xl"
|
||||
>
|
||||
<Image
|
||||
@@ -161,7 +153,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>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.18.3",
|
||||
"dependencies": {
|
||||
"@mintel/annotator": "file:./mintel-annotator-1.0.7.tgz",
|
||||
"@mintel/mail": "1.9.5",
|
||||
"@mintel/next-config": "1.9.5",
|
||||
"@mintel/next-feedback": "1.9.5",
|
||||
@@ -25,6 +26,7 @@
|
||||
"ioredis": "^5.9.3",
|
||||
"jsdom": "^27.4.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"mintel-annotator": "link:mintel-annotator",
|
||||
"next": "16.1.6",
|
||||
"next-i18next": "^15.4.3",
|
||||
"next-intl": "^4.8.2",
|
||||
|
||||
74
pnpm-lock.yaml
generated
74
pnpm-lock.yaml
generated
@@ -12,6 +12,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@mintel/annotator':
|
||||
specifier: file:./mintel-annotator-1.0.6.tgz
|
||||
version: file:mintel-annotator-1.0.6.tgz(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@mintel/mail':
|
||||
specifier: 1.9.5
|
||||
version: 1.9.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -75,6 +78,9 @@ importers:
|
||||
leaflet:
|
||||
specifier: ^1.9.4
|
||||
version: 1.9.4
|
||||
mintel-annotator:
|
||||
specifier: link:mintel-annotator
|
||||
version: link:mintel-annotator
|
||||
next:
|
||||
specifier: 16.1.6
|
||||
version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.98.0)
|
||||
@@ -1401,6 +1407,13 @@ packages:
|
||||
'@medv/finder@4.0.2':
|
||||
resolution: {integrity: sha512-RraNY9SCcx4KZV0Dh6BEW6XEW2swkqYca74pkFFRw6hHItSHiy+O/xMnpbofjYbzXj0tSpBGthUF1hHTsr3vIQ==}
|
||||
|
||||
'@mintel/annotator@file:mintel-annotator-1.0.6.tgz':
|
||||
resolution: {integrity: sha512-QkIiEShJexvzaQ9zANPi1zRIiBiBHEEsUu91AbtL3cpvMT/ApT31PFqu4cyho3QhzWOBhsdzNBbL0cQcFhR6nA==, tarball: file:mintel-annotator-1.0.6.tgz}
|
||||
version: 1.0.6
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
|
||||
'@mintel/eslint-config@1.9.11':
|
||||
resolution: {integrity: sha512-g0FfWbSjfNuKAWa9yd4eC02mr9db66Y71TZ+FN3aUrLLMjwn3qqtEHez+8Dbray0loLiGDkm75EI7N0+mgA9yA==, tarball: https://git.infra.mintel.me/api/packages/mmintel/npm/%40mintel%2Feslint-config/-/1.9.11/eslint-config-1.9.11.tgz}
|
||||
|
||||
@@ -4938,6 +4951,20 @@ packages:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
framer-motion@12.40.0:
|
||||
resolution: {integrity: sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==}
|
||||
peerDependencies:
|
||||
'@emotion/is-prop-valid': '*'
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
peerDependenciesMeta:
|
||||
'@emotion/is-prop-valid':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
fresh@0.5.2:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -5931,6 +5958,11 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
lucide-react@1.21.0:
|
||||
resolution: {integrity: sha512-reEZMXq8Qdd5jg5XYkQ5TR1fB/GiQ7ih4vcrthYDtgjSDwh0i6/YLiGjsWsIwgN49gpAnd4J2elSNzncMEEUUQ==}
|
||||
peerDependencies:
|
||||
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
lz-string@1.5.0:
|
||||
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
|
||||
hasBin: true
|
||||
@@ -6236,12 +6268,18 @@ packages:
|
||||
motion-dom@12.38.0:
|
||||
resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
|
||||
|
||||
motion-dom@12.40.0:
|
||||
resolution: {integrity: sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==}
|
||||
|
||||
motion-utils@11.18.1:
|
||||
resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==}
|
||||
|
||||
motion-utils@12.36.0:
|
||||
resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==}
|
||||
|
||||
motion-utils@12.39.0:
|
||||
resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==}
|
||||
|
||||
mri@1.2.0:
|
||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -7505,6 +7543,9 @@ packages:
|
||||
tailwind-merge@3.5.0:
|
||||
resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
|
||||
|
||||
tailwind-merge@3.6.0:
|
||||
resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
|
||||
|
||||
tailwindcss@4.2.2:
|
||||
resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
|
||||
|
||||
@@ -9317,6 +9358,18 @@ snapshots:
|
||||
|
||||
'@medv/finder@4.0.2': {}
|
||||
|
||||
'@mintel/annotator@file:mintel-annotator-1.0.6.tgz(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@medv/finder': 4.0.2
|
||||
clsx: 2.1.1
|
||||
framer-motion: 12.40.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
lucide-react: 1.21.0(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
tailwind-merge: 3.6.0
|
||||
transitivePeerDependencies:
|
||||
- '@emotion/is-prop-valid'
|
||||
|
||||
'@mintel/eslint-config@1.9.11(@typescript-eslint/parser@8.58.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint/eslintrc': 3.3.5
|
||||
@@ -13205,6 +13258,15 @@ snapshots:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
framer-motion@12.40.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
motion-dom: 12.40.0
|
||||
motion-utils: 12.39.0
|
||||
tslib: 2.8.1
|
||||
optionalDependencies:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
fresh@0.5.2: {}
|
||||
|
||||
from@0.1.7: {}
|
||||
@@ -14314,6 +14376,10 @@ snapshots:
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
lucide-react@1.21.0(react@19.2.4):
|
||||
dependencies:
|
||||
react: 19.2.4
|
||||
|
||||
lz-string@1.5.0: {}
|
||||
|
||||
maath@0.10.8(@types/three@0.183.1)(three@0.183.2):
|
||||
@@ -14849,10 +14915,16 @@ snapshots:
|
||||
dependencies:
|
||||
motion-utils: 12.36.0
|
||||
|
||||
motion-dom@12.40.0:
|
||||
dependencies:
|
||||
motion-utils: 12.39.0
|
||||
|
||||
motion-utils@11.18.1: {}
|
||||
|
||||
motion-utils@12.36.0: {}
|
||||
|
||||
motion-utils@12.39.0: {}
|
||||
|
||||
mri@1.2.0: {}
|
||||
|
||||
mrmime@2.0.1: {}
|
||||
@@ -16436,6 +16508,8 @@ snapshots:
|
||||
|
||||
tailwind-merge@3.5.0: {}
|
||||
|
||||
tailwind-merge@3.6.0: {}
|
||||
|
||||
tailwindcss@4.2.2: {}
|
||||
|
||||
tapable@2.3.2: {}
|
||||
|
||||
@@ -5,6 +5,8 @@ module.exports = {
|
||||
'./app/**/*.{js,ts,jsx,tsx}',
|
||||
'./components/**/*.{js,ts,jsx,tsx}',
|
||||
'./content/**/*.{mdx,md}',
|
||||
'./node_modules/@mintel/annotator/dist/**/*.{js,ts,jsx,tsx,mjs}',
|
||||
'./node_modules/.pnpm/@mintel+annotator*/node_modules/@mintel/annotator/dist/**/*.{js,ts,jsx,tsx,mjs}'
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
|
||||
Reference in New Issue
Block a user