feat: complete MDX migration, stabilize environment & verify via E2E tests

Former-commit-id: ec3e64156a2e182535cbdcf0d975cd54603a517d
This commit is contained in:
2026-05-03 18:46:41 +02:00
parent e57661a586
commit b13f628b55
131 changed files with 1388 additions and 25288 deletions

View File

@@ -1,17 +0,0 @@
import configPromise from '@payload-config';
import { RootPage } from '@payloadcms/next/views';
import { importMap } from '../importMap';
type Args = {
params: Promise<{
segments: string[];
}>;
searchParams: Promise<{
[key: string]: string | string[];
}>;
};
const Page = ({ params, searchParams }: Args) =>
RootPage({ config: configPromise, importMap, params, searchParams });
export default Page;

View File

@@ -1,57 +0,0 @@
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
import { BlocksFeatureClient as BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { InlineToolbarFeatureClient as InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HorizontalRuleFeatureClient as HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UploadFeatureClient as UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BlockquoteFeatureClient as BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { RelationshipFeatureClient as RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { LinkFeatureClient as LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ChecklistFeatureClient as ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { OrderedListFeatureClient as OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UnorderedListFeatureClient as UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { IndentFeatureClient as IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { AlignFeatureClient as AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { HeadingFeatureClient as HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ParagraphFeatureClient as ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { InlineCodeFeatureClient as InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { SuperscriptFeatureClient as SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { SubscriptFeatureClient as SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
import { default as default_9ed509b5e5f7d08a16335393f27586cc } from '../../../src/payload/components/Icon'
import { default as default_5470ea90f7a8fd882c2fe59ff2b1c5b9 } from '../../../src/payload/components/Logo'
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
export const importMap = {
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
"@payloadcms/richtext-lexical/client#BlocksFeatureClient": BlocksFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#InlineToolbarFeatureClient": InlineToolbarFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HorizontalRuleFeatureClient": HorizontalRuleFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UploadFeatureClient": UploadFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BlockquoteFeatureClient": BlockquoteFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#RelationshipFeatureClient": RelationshipFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#LinkFeatureClient": LinkFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ChecklistFeatureClient": ChecklistFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#OrderedListFeatureClient": OrderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnorderedListFeatureClient": UnorderedListFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#IndentFeatureClient": IndentFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#AlignFeatureClient": AlignFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#HeadingFeatureClient": HeadingFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ParagraphFeatureClient": ParagraphFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#InlineCodeFeatureClient": InlineCodeFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SuperscriptFeatureClient": SuperscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#SubscriptFeatureClient": SubscriptFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#StrikethroughFeatureClient": StrikethroughFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
"/src/payload/components/Icon#default": default_9ed509b5e5f7d08a16335393f27586cc,
"/src/payload/components/Logo#default": default_5470ea90f7a8fd882c2fe59ff2b1c5b9,
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
}

View File

@@ -1,14 +0,0 @@
import config from '@payload-config';
import {
REST_GET,
REST_OPTIONS,
REST_PATCH,
REST_POST,
REST_DELETE,
} from '@payloadcms/next/routes';
export const GET = REST_GET(config);
export const POST = REST_POST(config);
export const DELETE = REST_DELETE(config);
export const PATCH = REST_PATCH(config);
export const OPTIONS = REST_OPTIONS(config);

View File

@@ -1,4 +0,0 @@
import config from '@payload-config';
import { GRAPHQL_POST } from '@payloadcms/next/routes';
export const POST = GRAPHQL_POST(config);

View File

@@ -1,151 +0,0 @@
/* =================================================================
KLZ Cables Payload Admin Theme
Strictly follows docs/STYLEGUIDE.md & tailwind.config.cjs
IMPORTANT: We use `html` selector (not `:root`) because Payload's
own CSS defines variables on `:root` and loads AFTER this file.
`html` has higher specificity than `:root`, so our values win.
================================================================= */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
/* =================================================================
COLOR OVERRIDES
Payload internally maps:
--theme-elevation-* → --color-base-*
--theme-success-* → --color-success-*
We override the SOURCE variables on `html` to beat Payload's `:root`.
================================================================= */
html {
/* ---------------------------------------------------------------
KLZ Primary Blue (#011dff) → Buttons, links, active states
--------------------------------------------------------------- */
--color-success-50: #eef0ff !important;
--color-success-100: #dfe2ff !important;
--color-success-150: #cdd2ff !important;
--color-success-200: #b8bfff !important;
--color-success-250: #a0a9ff !important;
--color-success-300: #8892ff !important;
--color-success-350: #707bff !important;
--color-success-400: #5564ff !important;
--color-success-450: #3a4dff !important;
--color-success-500: #011dff !important;
/* KLZ Primary */
--color-success-550: #0119e6 !important;
--color-success-600: #0116cc !important;
--color-success-650: #0112b3 !important;
--color-success-700: #000e99 !important;
--color-success-750: #000b80 !important;
--color-success-800: #000866 !important;
--color-success-850: #00054d !important;
--color-success-900: #000333 !important;
--color-success-950: #00011a !important;
/* ---------------------------------------------------------------
KLZ "Foundation Neutrals" → Backgrounds, cards, borders, text
Based on tailwind.config.cjs: neutral.light=#fff,
neutral.DEFAULT=#f8f9fa, neutral.dark=#263336, neutral.black=#0a0a0a
text.primary=#1a1a1a, text.secondary=#6c757d, text.light=#adb5bd
--------------------------------------------------------------- */
--color-base-0: #ffffff !important;
--color-base-50: #f8f9fa !important;
--color-base-100: #f1f3f5 !important;
--color-base-150: #e9ecef !important;
--color-base-200: #dee2e6 !important;
--color-base-250: #ced4da !important;
--color-base-300: #adb5bd !important;
--color-base-350: #9ba3ab !important;
--color-base-400: #868e96 !important;
--color-base-450: #6c757d !important;
--color-base-500: #5c636a !important;
--color-base-550: #4d5358 !important;
--color-base-600: #3d4246 !important;
--color-base-650: #343a40 !important;
--color-base-700: #2b3035 !important;
--color-base-750: #263336 !important;
--color-base-800: #212529 !important;
--color-base-850: #1a1a1a !important;
--color-base-900: #121212 !important;
--color-base-950: #0a0a0a !important;
--color-base-1000: #000000 !important;
/* Typography */
--font-body: 'Inter', system-ui, -apple-system, sans-serif !important;
--font-headings: 'Inter', system-ui, -apple-system, sans-serif !important;
}
/* Base Body Application */
body {
font-family: var(--font-body) !important;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* =================================================================
Login / Setup Page
================================================================= */
.template-default.template-default--has-bg {
background: radial-gradient(circle at top right, #e6ebf5 0%, #f8f9fa 60%, #f3f4f6 100%) !important;
}
.login__wrap,
.create-first-user__wrap {
border-top: none !important;
padding: 3rem !important;
background: rgba(255, 255, 255, 0.85) !important;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid var(--theme-elevation-150) !important;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15) !important;
border-radius: 1.5rem !important;
}
/* =================================================================
Buttons override Payload's dark buttons with KLZ Blue
Payload uses .btn--style-primary { --bg-color: var(--theme-elevation-800) }
which makes all primary buttons near-black. We override to KLZ Blue.
================================================================= */
.btn--style-primary,
.btn--style-pill {
--bg-color: #011dff !important;
--color: #ffffff !important;
--hover-bg: #0116cc !important;
--hover-color: #ffffff !important;
}
.btn--style-primary.btn--disabled,
.btn--style-pill.btn--disabled {
--bg-color: #b8bfff !important;
--color: #ffffff !important;
--hover-bg: #b8bfff !important;
}
/* Sidebar Active Items */
[class*="nav-group__link--active"],
[class*="nav__link--active"] {
--theme-elevation-800: #011dff !important;
color: #011dff !important;
border-left-color: #011dff !important;
}
.btn--style-secondary {
--box-shadow: inset 0 0 0 1px #011dff !important;
--color: #011dff !important;
--hover-color: #0116cc !important;
--hover-box-shadow: inset 0 0 0 1px #0116cc !important;
}
/* =================================================================
Logo & Icon
================================================================= */
.klz-admin-logo,
.klz-admin-icon {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
height: 32px !important;
width: auto !important;
max-width: 100% !important;
object-fit: contain !important;
}

View File

@@ -1,31 +0,0 @@
import configPromise from '@payload-config';
import { RootLayout } from '@payloadcms/next/layouts';
import React from 'react';
import '@payloadcms/next/css';
import './custom.scss';
import { handleServerFunctions } from '@payloadcms/next/layouts';
import { importMap } from './admin/importMap';
type Args = {
children: React.ReactNode;
};
const serverFunction: any = async function (args: any) {
'use server';
return handleServerFunctions({
...args,
config: configPromise,
importMap,
});
};
const Layout = ({ children }: Args) => {
return (
<RootLayout config={configPromise} importMap={importMap} serverFunction={serverFunction}>
{children}
</RootLayout>
);
};
export default Layout;

View File

@@ -1,20 +0,0 @@
export default function DatenschutzPage() {
return (
<div className="pt-32 pb-24">
<div className="container max-w-3xl">
<h1 className="text-5xl font-heading font-extrabold text-neutral-dark mb-8">Datenschutzerklärung</h1>
<div className="prose prose-neutral max-w-none">
<h3>1. Datenschutz auf einen Blick</h3>
<p>
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen.
Die vollständige Datenschutzerklärung der E-TIB GmbH wird aktuell überarbeitet und an dieser Stelle zur Verfügung gestellt.
</p>
<h3>2. Allgemeine Hinweise und Pflichtinformationen</h3>
<p>
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.
</p>
</div>
</div>
</div>
);
}

View File

@@ -1,37 +0,0 @@
export default function ImpressumPage() {
return (
<div className="pt-32 pb-24">
<div className="container max-w-3xl">
<h1 className="text-5xl font-heading font-extrabold text-neutral-dark mb-8">Impressum</h1>
<div className="prose prose-neutral max-w-none">
<p><strong>Angaben gemäß § 5 TMG</strong></p>
<p>
E-TIB GmbH<br />
Gewerbestraße 22<br />
03172 Guben
</p>
<p>
<strong>Vertreten durch:</strong><br />
Geschäftsführer: Danny Joseph
</p>
<p>
<strong>Kontakt:</strong><br />
Telefon: +49 15207230518<br />
E-Mail: d.joseph@e-tib.com
</p>
<p>
<strong>Registereintrag:</strong><br />
Eintragung im Handelsregister.<br />
Registergericht: Amtsgericht Cottbus<br />
Registernummer: HRB 12403 CB
</p>
<p>
<strong>Umsatzsteuer-ID:</strong><br />
Umsatzsteuer-Identifikationsnummer gemäß § 27 a Umsatzsteuergesetz:<br />
DE304799919
</p>
</div>
</div>
</div>
);
}

View File

@@ -4,10 +4,23 @@ import { getTranslations, setRequestLocale } from 'next-intl/server';
import { Metadata } from 'next';
import { getPageBySlug } from '@/lib/pages';
import { mapSlugToFileSlug, mapFileSlugToTranslated } from '@/lib/slugs';
import PayloadRichText from '@/components/PayloadRichText';
import { MDXRemote } from 'next-mdx-remote/rsc';
import { SITE_URL } from '@/lib/schema';
import TrackedLink from '@/components/analytics/TrackedLink';
// Import components used in MDX
import { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';
import { SubCompanyTiles as HomeSubCompanyTiles } from '@/components/blocks/SubCompanyTiles';
import { CompetenceBentoGrid as HomeCompetenceBentoGrid } from '@/components/blocks/CompetenceBentoGrid';
import { ReferencesSlider as HomeReferencesSlider } from '@/components/blocks/ReferencesSlider';
const mdxComponents = {
HomeHero,
HomeSubCompanyTiles,
HomeCompetenceBentoGrid,
HomeReferencesSlider,
};
interface PageProps {
params: Promise<{
locale: string;
@@ -72,18 +85,20 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
notFound();
}
// Redirect if accessed via a different locale's slug
// Redirect logic removed to simplify for now, as slugs match file names
/*
const fileSlug = await mapSlugToFileSlug(pageData.slug || slug, locale);
const correctSlug = await mapFileSlugToTranslated(fileSlug, locale);
if (correctSlug && correctSlug !== slug) {
redirect(`/${locale}/${correctSlug}`);
}
*/
// Full-bleed pages render blocks edge-to-edge without the generic article wrapper
if (pageData.frontmatter.layout === 'fullBleed') {
return (
<div className="flex flex-col min-h-screen">
<PayloadRichText data={pageData.content} className="" />
<MDXRemote source={pageData.content} components={mdxComponents} />
</div>
);
}
@@ -120,9 +135,9 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
</div>
)}
{/* Main content with shared blog components */}
{/* Main content */}
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary">
<PayloadRichText data={pageData.content} />
<MDXRemote source={pageData.content} components={mdxComponents} />
</div>
{/* Support Section */}

View File

@@ -5,7 +5,7 @@ import {
getPostBySlug,
getAdjacentPosts,
getReadingTime,
extractLexicalHeadings,
getHeadings,
} from '@/lib/blog';
import { Metadata } from 'next';
import Link from 'next/link';
@@ -16,9 +16,7 @@ import TableOfContents from '@/components/blog/TableOfContents';
import { Heading } from '@/components/ui';
import { setRequestLocale } from 'next-intl/server';
import BlogEngagementTracker from '@/components/analytics/BlogEngagementTracker';
// Payload CMS Imports
import PayloadRichText from '@/components/PayloadRichText';
import { MDXRemote } from 'next-mdx-remote/rsc';
interface BlogPostProps {
params: Promise<{
@@ -65,20 +63,9 @@ export default async function BlogPost({ params }: BlogPostProps) {
notFound();
}
// If the user accessed this post using a slug from a different locale
// (e.g. via the generic language switcher), redirect them to the correct localized slug URL
if (post.slug && post.slug !== slug) {
redirect(`/${locale}/blog/${post.slug}`);
}
const { prev, next, isPrevRandom, isNextRandom } = await getAdjacentPosts(post.slug, locale);
// Convert Lexical content into a plain string to estimate reading time roughly
// Extract headings for TOC
const headings = extractLexicalHeadings(post.content?.root || post.content);
// Convert Lexical content into a plain string to estimate reading time roughly
const rawTextContent = JSON.stringify(post.content);
const headings = getHeadings(post.content);
const readingTime = getReadingTime(post.content);
return (
<article className="bg-white min-h-screen font-sans selection:bg-primary/10 selection:text-primary">
@@ -86,7 +73,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
title={post.frontmatter.title}
slug={slug}
category={post.frontmatter.category}
readingTime={getReadingTime(rawTextContent)}
readingTime={readingTime}
/>
{/* Featured Image Header */}
@@ -131,7 +118,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
})}
</time>
<span className="w-1 h-1 bg-white/30 rounded-full" />
<span>{getReadingTime(rawTextContent)} min read</span>
<span>{readingTime} min read</span>
{(new Date(post.frontmatter.date) > new Date() ||
post.frontmatter.public === false) && (
<>
@@ -168,7 +155,7 @@ export default async function BlogPost({ params }: BlogPostProps) {
})}
</time>
<span className="w-1 h-1 bg-neutral-400 rounded-full" />
<span>{getReadingTime(rawTextContent)} min read</span>
<span>{readingTime} min read</span>
{(new Date(post.frontmatter.date) > new Date() ||
post.frontmatter.public === false) && (
<>
@@ -197,9 +184,9 @@ export default async function BlogPost({ params }: BlogPostProps) {
</div>
)}
{/* Main content with enhanced styling rendering Payload Lexical */}
{/* Main content */}
<div className="prose prose-lg md:prose-xl max-w-none prose-headings:font-bold prose-headings:text-text-primary prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-img:rounded-2xl prose-img:shadow-2xl prose-blockquote:border-primary prose-blockquote:bg-primary/5 prose-blockquote:rounded-r-2xl prose-strong:text-primary">
<PayloadRichText data={post.content} />
<MDXRemote source={post.content} />
</div>
{/* Power CTA */}
@@ -284,8 +271,8 @@ export default async function BlogPost({ params }: BlogPostProps) {
'@id': `${SITE_URL}/${locale}/blog/${slug}`,
},
articleSection: post.frontmatter.category,
wordCount: rawTextContent.split(/\s+/).length,
timeRequired: `PT${getReadingTime(rawTextContent)}M`,
wordCount: post.content.split(/\s+/).length,
timeRequired: `PT${readingTime}M`,
} as any
}
/>

View File

@@ -62,7 +62,8 @@ export default async function ContactPage({ params }: ContactPageProps) {
setRequestLocale(locale);
const t = await getTranslations({ locale, namespace: 'Contact' });
// Get translated slug to redirect if user used incorrect static slug
// Get translated slug - redirect logic removed to prevent loop with next.config.js rewrites
/*
const { headers } = await import('next/headers');
const headersList = await headers();
const urlPath = headersList.get('x-invoke-path') || '';
@@ -75,6 +76,7 @@ export default async function ContactPage({ params }: ContactPageProps) {
redirect(`/${locale}/${contactSlugDe}`);
}
}
*/
return (
<div className="flex flex-col min-h-screen bg-neutral-light">

View File

@@ -2,7 +2,6 @@ import { Footer } from '@/components/layout/Footer';
import { Header } from '@/components/layout/Header';
import JsonLd from '@/components/JsonLd';
import SkipLink from '@/components/SkipLink';
import CMSConnectivityNotice from '@/components/CMSConnectivityNotice';
import AnalyticsShell from '@/components/analytics/AnalyticsShell';
import { Metadata, Viewport } from 'next';
import { NextIntlClientProvider } from 'next-intl';
@@ -12,8 +11,7 @@ import { SITE_URL } from '@/lib/schema';
import FeedbackClientWrapper from '@/components/FeedbackClientWrapper';
import { setRequestLocale } from 'next-intl/server';
import { Inter } from 'next/font/google';
import { getPayload } from 'payload';
import configPromise from '@payload-config';
import { mapFileSlugToTranslated } from '@/lib/slugs';
const inter = Inter({
subsets: ['latin'],
@@ -80,32 +78,30 @@ export default async function Layout(props: {
let messages: Record<string, any> = {};
try {
messages = await getMessages();
} catch {
} catch (err) {
console.error('[Layout] Failed to load messages:', err);
messages = {};
}
// Fetch navigation and company info from Payload Global Settings
const payload = await getPayload({ config: configPromise });
const settings = await payload.findGlobal({
slug: 'settings',
locale: safeLocale as any,
}).catch(() => null);
const navLinks = (settings?.mainNav && settings.mainNav.length > 0)
? settings.mainNav.map((n) => ({
label: n.label,
url: n.url,
}))
: [
{ label: 'Kompetenzen', url: `/${safeLocale}/kompetenzen` },
{ label: 'Über uns', url: `/${safeLocale}/ueber-uns` },
{ label: 'Karriere & Messen', url: `/${safeLocale}/karriere` }
const navLinks = [
{
label: safeLocale === 'de' ? 'Kompetenzen' : 'Competencies',
url: `/${safeLocale}/${await mapFileSlugToTranslated('kompetenzen', safeLocale)}`
},
{
label: safeLocale === 'de' ? 'Über uns' : 'About Us',
url: `/${safeLocale}/${await mapFileSlugToTranslated('ueber-uns', safeLocale)}`
},
{
label: safeLocale === 'de' ? 'Karriere & Messen' : 'Careers & Events',
url: `/${safeLocale}/${await mapFileSlugToTranslated('karriere', safeLocale)}`
}
];
const companyInfo = {
contactEmail: settings?.contactEmail || 'info@e-tib.com',
contactPhone: settings?.contactPhone || '+49 15207230518',
address: settings?.address || 'Gewerbestraße 22\\n03172 Guben'
contactEmail: 'info@e-tib.com',
contactPhone: '+49 15207230518',
address: 'Gewerbestraße 22\n03172 Guben'
};
// Pick only the namespaces required by client components to reduce the hydration payload size
@@ -185,7 +181,6 @@ export default async function Layout(props: {
<Footer companyInfo={companyInfo} />
<JsonLd />
<CMSConnectivityNotice />
<AnalyticsShell />
{feedbackEnabled && <FeedbackClientWrapper feedbackEnabled={feedbackEnabled} />}
</NextIntlClientProvider>

View File

@@ -1,87 +1,19 @@
import { getTranslations } from 'next-intl/server';
import { Container, Button, Heading } from '@/components/ui';
import Scribble from '@/components/Scribble';
import { getPayload } from 'payload';
import configPromise from '@payload-config';
import { headers } from 'next/headers';
import ClientNotFoundTracker from '@/components/analytics/ClientNotFoundTracker';
export default async function NotFound() {
console.log('--- NOT FOUND RENDERED ---');
const t = await getTranslations('Error.notFound');
// Try to determine the requested path
const headersList = await headers();
const urlPath = headersList.get('x-invoke-path') || '';
// Suggested URL logic removed along with Payload CMS
let suggestedUrl = null;
let suggestedLang = null;
// If we have a path, try to see if the last segment (slug) exists in ANY locale
if (urlPath) {
const slug = urlPath.split('/').filter(Boolean).pop();
if (slug) {
try {
const payload = await getPayload({ config: configPromise });
// Check posts
const postRes = await payload.find({
collection: 'posts',
where: { slug: { equals: slug } },
locale: 'all',
limit: 1,
});
// Check competences
const competenceRes =
postRes.docs.length === 0
? await payload.find({
collection: 'competences',
where: { slug: { equals: slug } },
locale: 'all',
limit: 1,
})
: { docs: [] };
// Check pages
const pageRes =
postRes.docs.length === 0 && competenceRes.docs.length === 0
? await payload.find({
collection: 'pages',
where: { slug: { equals: slug } },
locale: 'all',
limit: 1,
})
: { docs: [] };
const anyDoc = postRes.docs[0] || competenceRes.docs[0] || pageRes.docs[0];
if (anyDoc) {
// If the doc exists, we can figure out its native locale or
// offer the alternative locale (if we are in 'de', offer 'en')
const currentLocale = urlPath.startsWith('/en') ? 'en' : 'de';
const alternativeLocale = currentLocale === 'de' ? 'en' : 'de';
suggestedLang = alternativeLocale === 'de' ? 'Deutsch' : 'English';
// Reconstruct the URL for the alternative locale
const pathParts = urlPath.split('/').filter(Boolean);
if (pathParts.length > 0 && (pathParts[0] === 'en' || pathParts[0] === 'de')) {
pathParts[0] = alternativeLocale;
} else {
pathParts.unshift(alternativeLocale);
}
suggestedUrl = '/' + pathParts.join('/');
}
} catch {
// Ignore Payload errors in 404
}
}
}
return (
<>
<ClientNotFoundTracker path={urlPath} />
<ClientNotFoundTracker path="" />
<Container className="relative py-24 flex flex-col items-center justify-center text-center min-h-[70vh] overflow-hidden">
{/* Industrial Background Element */}
<div className="absolute inset-0 -z-10 opacity-[0.03] pointer-events-none flex items-center justify-center">

View File

@@ -1,80 +1,52 @@
import { Metadata } from 'next';
import { setRequestLocale } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { getPayload } from 'payload';
import configPromise from '@payload-config';
import PayloadRichText from '@/components/PayloadRichText';
import { getMdxContent } from '@/lib/mdx';
import { MDXRemote } from 'next-mdx-remote/rsc';
// Import components used in MDX
import { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';
import { SubCompanyTiles as HomeSubCompanyTiles } from '@/components/blocks/SubCompanyTiles';
import { CompetenceBentoGrid as HomeCompetenceBentoGrid } from '@/components/blocks/CompetenceBentoGrid';
import { ReferencesSlider as HomeReferencesSlider } from '@/components/blocks/ReferencesSlider';
const mdxComponents = {
HomeHero,
HomeSubCompanyTiles,
HomeCompetenceBentoGrid,
HomeReferencesSlider,
};
export async function generateMetadata(props: any): Promise<Metadata> {
const { locale } = await props.params;
if (locale !== 'de' && locale !== 'en') return {};
const mdx = await getMdxContent(locale, 'home');
return {
title: locale === 'de' ? 'Startseite' : 'Home',
title: mdx?.frontmatter?.title || (locale === 'de' ? 'Startseite' : 'Home'),
description: mdx?.frontmatter?.description,
};
}
export default async function Home(props: any) {
const { locale } = await props.params;
// Guard against invalid locales (e.g. favicon.ico) hitting the DB
if (locale !== 'de' && locale !== 'en') {
notFound();
}
setRequestLocale(locale);
const payload = await getPayload({ config: configPromise });
const mdx = await getMdxContent(locale, 'home');
// 1. Fetch references for the ReferencesSlider (passed globally to PayloadRichText)
const { docs: references } = await payload.find({
collection: 'references',
locale: locale as any,
limit: 10,
});
const mappedReferences = references.map((ref: any) => ({
id: ref.id,
title: ref.title,
slug: ref.slug,
category: ref.category,
image: ref.image,
}));
// 2. Fetch the Home page content
const { docs: pages } = await payload.find({
collection: 'pages',
where: {
slug: {
equals: 'home',
},
},
locale: locale as any,
limit: 1,
draft: true, // Allow finding drafts in development
});
const page = pages[0];
// Fallback to static components if page doesn't exist in CMS yet
// This prevents the site from breaking before the first seed/save
if (!page) {
console.warn(`[Home] Page slug "home" not found in CMS for locale "${locale}".`);
const { totalDocs: allPages } = await payload.find({ collection: 'pages', locale: 'all' });
console.log(`[Home] Total pages in CMS (all locales): ${allPages}`);
return (
<main className="min-h-screen flex items-center justify-center bg-neutral-dark text-white p-10 text-center">
<div>
<h1 className="text-4xl font-bold mb-4">Willkommen bei E-TIB</h1>
<p className="text-xl text-white/60">Die CMS-Inhalte für diese Seite müssen noch angelegt werden.</p>
</div>
</main>
);
if (!mdx) {
notFound();
}
return (
<main>
<PayloadRichText data={page.content} references={mappedReferences} />
<MDXRemote source={mdx.content} components={mdxComponents} />
</main>
);
}

View File

@@ -41,28 +41,7 @@ export async function requestBrochureAction(formData: FormData) {
return { success: false, error: 'Invalid email address' };
}
// 1. Save to CMS
try {
const { getPayload } = await import('payload');
const configPromise = (await import('@payload-config')).default;
const payload = await getPayload({ config: configPromise });
await payload.create({
collection: 'form-submissions',
data: {
name: email.split('@')[0],
email,
message: `Brochure download request (${locale})`,
type: 'brochure_download' as any,
},
overrideAccess: true,
});
logger.info('Successfully saved brochure request to Payload CMS', { email });
} catch (error) {
logger.error('Failed to store brochure request in Payload CMS', { error });
services.errors.captureException(error, { action: 'payload_store_brochure_request' });
}
// 1. Save to CMS - Removed along with Payload CMS
// 2. Notify via Gotify
try {

View File

@@ -47,32 +47,7 @@ export async function sendContactFormAction(formData: FormData) {
return { success: false, error: 'Missing required fields' };
}
// 1. Save to CMS
try {
const { getPayload } = await import('payload');
const configPromise = (await import('@payload-config')).default;
const payload = await getPayload({ config: configPromise });
await payload.create({
collection: 'form-submissions',
data: {
name,
email,
message,
type: productName ? 'product_quote' : 'contact',
productName: productName || undefined,
},
overrideAccess: true,
});
logger.info('Successfully saved form submission to Payload CMS', {
type: productName ? 'product_quote' : 'contact',
email,
});
} catch (error) {
logger.error('Failed to store submission in Payload CMS', { error });
services.errors.captureException(error, { action: 'payload_store_submission' });
}
// 1. Save to CMS - Removed along with Payload CMS
// 2. Send Emails
logger.info('Sending branded emails', { email, productName });

View File

@@ -1,41 +0,0 @@
import { NextResponse } from 'next/server';
import { getPayload } from 'payload';
import configPromise from '@payload-config';
export const dynamic = 'force-dynamic';
/**
* Deep CMS Health Check
* Validates that Payload CMS can actually query the database.
* Used by post-deploy smoke tests to catch migration/schema issues.
*/
export async function GET() {
const checks: Record<string, string> = {};
try {
const payload = await getPayload({ config: configPromise });
checks.init = 'ok';
// Verify each collection can be queried (catches missing locale tables, broken migrations)
const collections = ['posts', 'pages', 'media', 'competences'] as const;
for (const collection of collections) {
try {
await payload.find({ collection, limit: 1 });
checks[collection] = 'ok';
} catch (e: any) {
checks[collection] = `error: ${e.message?.substring(0, 100)}`;
}
}
const hasErrors = Object.values(checks).some((v) => v.startsWith('error'));
return NextResponse.json(
{ status: hasErrors ? 'degraded' : 'ok', checks },
{ status: hasErrors ? 503 : 200 },
);
} catch (e: any) {
return NextResponse.json(
{ status: 'error', message: e.message?.substring(0, 200), checks },
{ status: 503 },
);
}
}

View File

@@ -1,86 +0,0 @@
'use client';
import React, { useEffect, useState } from 'react';
import { AlertCircle, RefreshCw } from 'lucide-react';
import { config } from '../lib/config';
export default function CMSConnectivityNotice() {
const [, setStatus] = useState<'checking' | 'ok' | 'error'>('checking');
const [errorMsg, setErrorMsg] = useState('');
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
// Only show if we've detected an issue AND we are in a context where we want to see it
const checkCMS = async () => {
const isDebug = new URLSearchParams(window.location.search).has('cms_debug');
const isLocal = config.isDevelopment;
const isTesting = config.isTesting;
const target = process.env.NEXT_PUBLIC_TARGET || '';
const isBranch = target === 'branch';
// Only proceed with check if it's developer context (Local, Testing, or Branch preview)
// Staging and Production should NEVER see this unless forced with ?cms_debug
if (!isLocal && !isTesting && !isBranch && !isDebug) return;
try {
const response = await fetch('/api/health/cms');
const data = await response.json();
if (data.status !== 'ok') {
setStatus('error');
setErrorMsg(data.message);
setIsVisible(true);
} else {
setStatus('ok');
setIsVisible(false);
}
} catch {
// If it's a connection error, only show if we are really debugging
if (isDebug || isLocal) {
setStatus('error');
setErrorMsg('Could not connect to CMS health endpoint');
setIsVisible(true);
}
}
};
checkCMS();
}, []);
if (!isVisible) return null;
return (
<div className="fixed bottom-4 right-4 z-[9999] animate-slide-up">
<div className="bg-red-500/90 backdrop-blur-md border border-red-400 text-white p-4 rounded-2xl shadow-2xl max-w-sm">
<div className="flex items-start gap-3">
<div className="bg-white/20 p-2 rounded-lg">
<AlertCircle className="w-5 h-5" />
</div>
<div className="flex-1">
<h4 className="font-bold text-sm mb-1">CMS Issue Detected</h4>
<p className="text-xs opacity-90 leading-relaxed mb-3">
{errorMsg === 'relation "products" does not exist'
? 'The database schema is missing. Please run migrations for this environment.'
: 'A content service is unavailable. Check the deployment logs for details.'}
</p>
<div className="flex gap-2">
<button
onClick={() => window.location.reload()}
className="bg-white text-red-600 text-[10px] font-bold uppercase tracking-wider px-3 py-1.5 rounded-lg flex items-center gap-2 hover:bg-neutral-100 transition-colors"
>
<RefreshCw className="w-3 h-3" />
Retry
</button>
<button
onClick={() => setIsVisible(false)}
className="bg-black/20 text-white text-[10px] font-bold uppercase tracking-wider px-3 py-1.5 rounded-lg hover:bg-black/30 transition-colors"
>
Dismiss
</button>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,94 +0,0 @@
import { defaultJSXConverters, RichText } from '@payloadcms/richtext-lexical/react';
import type { JSXConverters } from '@payloadcms/richtext-lexical/react';
import Image from 'next/image';
import { Fragment } from 'react';
import { allBlocks } from '@/src/payload/blocks/allBlocks';
import ObfuscatedEmail from '@/components/ObfuscatedEmail';
import ObfuscatedPhone from '@/components/ObfuscatedPhone';
/**
* Splits a text string on \n and intersperses <br /> elements.
*/
function textWithLineBreaks(text: string, key: string) {
const parts = text.split('\n');
if (parts.length === 1) return text;
return parts.map((part, i) => (
<Fragment key={`${key}-${i}`}>
{part}
{i < parts.length - 1 && <br />}
</Fragment>
));
}
const jsxConverters: JSXConverters = {
...defaultJSXConverters,
linebreak: () => <br />,
text: ({ node }: any) => {
let content: React.ReactNode = node.text || '';
if (typeof content === 'string' && content.includes('\n')) {
content = textWithLineBreaks(content, `t-${(node.text || '').slice(0, 8)}`);
}
if (typeof content === 'string' && content.includes('@')) {
const emailRegex = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g;
const parts = content.split(emailRegex);
content = parts.map((part, i) => {
if (part.match(emailRegex)) {
return <ObfuscatedEmail key={`e-${i}`} email={part} />;
}
return part;
});
}
if (typeof content === 'string' && content.match(/\+\d+/)) {
const phoneRegex = /(\+\d{1,4}[\d\s-]{5,15})/g;
const parts = content.split(phoneRegex);
content = parts.map((part, i) => {
if (part.match(phoneRegex)) {
return <ObfuscatedPhone key={`p-${i}`} phone={part} />;
}
return part;
});
}
return content;
},
upload: ({ node }: any) => {
return (
<div className="relative w-full aspect-video my-8 rounded-3xl overflow-hidden shadow-2xl group">
<Image
src={node.value.url}
alt={node.value.alt || ''}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover transition-transform duration-700 group-hover:scale-105"
/>
</div>
);
},
blocks: allBlocks.reduce((acc, block) => {
const Component = block.render;
if (!Component) return acc;
const renderFn = ({ node }: any) => {
// Pass all fields as props to the component, and also as 'data' prop
return <Component {...node.fields} data={node.fields} />;
};
// Map both the direct slug and the block- prefixed slug
return {
...acc,
[block.slug]: renderFn,
[`block-${block.slug}`]: renderFn,
};
}, {}),
};
export default function PayloadRichText({ data, className, references }: { data: any; className?: string; references?: any[] }) {
if (!data) return null;
return (
<div className={className}>
<RichText data={data} converters={jsxConverters} />
</div>
);
}

View File

@@ -1,5 +1,6 @@
import Link from 'next/link';
import Image from 'next/image';
import { useLocale } from 'next-intl';
interface CompanyInfo {
contactEmail: string;
@@ -12,11 +13,13 @@ interface FooterProps {
}
export function Footer({ companyInfo }: FooterProps) {
const locale = useLocale();
return (
<footer className="bg-neutral-dark text-neutral-light py-16">
<div className="container grid grid-cols-1 md:grid-cols-4 gap-12">
<div className="col-span-1 md:col-span-2">
<Link href="/" className="relative block h-14 w-56 mb-6">
<Link href={`/${locale}`} className="relative block h-14 w-56 mb-6">
<Image
src="/assets/logo-white.png"
alt="E-TIB Gruppe"
@@ -38,17 +41,18 @@ export function Footer({ companyInfo }: FooterProps) {
<div>
<h4 className="font-heading font-semibold text-lg mb-4">Unternehmen</h4>
<ul className="space-y-2">
<li><Link href="/kompetenzen" className="text-text-light hover:text-primary-light transition-colors">Kompetenzen</Link></li>
<li><Link href="/ueber-uns" className="text-text-light hover:text-primary-light transition-colors">Über uns</Link></li>
<li><Link href="/karriere" className="text-text-light hover:text-primary-light transition-colors">Karriere / Jobs</Link></li>
<li><Link href="/kontakt" className="text-text-light hover:text-primary-light transition-colors">Kontakt</Link></li>
<li><Link href={`/${locale}/kompetenzen`} className="text-text-light hover:text-primary-light transition-colors">Kompetenzen</Link></li>
<li><Link href={`/${locale}/ueber-uns`} className="text-text-light hover:text-primary-light transition-colors">Über uns</Link></li>
<li><Link href={`/${locale}/karriere`} className="text-text-light hover:text-primary-light transition-colors">Karriere / Jobs</Link></li>
<li><Link href={`/${locale}/kontakt`} className="text-text-light hover:text-primary-light transition-colors">Kontakt</Link></li>
</ul>
</div>
<div>
<h4 className="font-heading font-semibold text-lg mb-4">Rechtliches</h4>
<ul className="space-y-2">
<li><Link href="/impressum" className="text-text-light hover:text-primary-light transition-colors">Impressum</Link></li>
<li><Link href="/datenschutz" className="text-text-light hover:text-primary-light transition-colors">Datenschutz</Link></li>
<li><Link href={`/${locale}/impressum`} className="text-text-light hover:text-primary-light transition-colors">Impressum</Link></li>
<li><Link href={`/${locale}/datenschutz`} className="text-text-light hover:text-primary-light transition-colors">Datenschutz</Link></li>
<li><Link href={`/${locale}/${locale === 'de' ? 'agb' : 'terms'}`} className="text-text-light hover:text-primary-light transition-colors">{locale === 'de' ? 'AGB' : 'Terms'}</Link></li>
<li><button className="text-text-light hover:text-primary-light transition-colors">Cookie-Einstellungen</button></li>
</ul>
</div>

View File

@@ -95,10 +95,10 @@ export function Header({ navLinks }: HeaderProps) {
<div className="h-4 w-px bg-white/20 mx-2" />
<LanguageSwitcher />
<Link
href={`/${currentLocale}/kontakt`}
href={`/${currentLocale}/${currentLocale === 'de' ? 'kontakt' : 'contact'}`}
className="rounded-xl bg-primary hover:bg-primary-dark text-neutral-light px-8 py-3 font-bold text-sm uppercase tracking-wider transition-all shadow-xl hover:shadow-primary/20 hover:-translate-y-0.5 active:translate-y-0"
>
Kontakt
{currentLocale === 'de' ? 'Kontakt' : 'Contact'}
</Link>
</nav>
@@ -154,10 +154,10 @@ export function Header({ navLinks }: HeaderProps) {
})}
<Link
href={`/${currentLocale}/kontakt`}
href={`/${currentLocale}/${currentLocale === 'de' ? 'kontakt' : 'contact'}`}
className="text-2xl font-heading font-bold text-primary border-b border-neutral-100 pb-4"
>
Kontakt
{currentLocale === 'de' ? 'Kontakt' : 'Contact'}
</Link>
<div className="mt-8 pt-6">

22
content/de/agb.mdx Normal file
View File

@@ -0,0 +1,22 @@
---
title: "Allgemeine Geschäftsbedingungen"
date: "2024-03-20"
excerpt: "Die Allgemeinen Geschäftsbedingungen der E-TIB GmbH für unsere Leistungen und Lieferungen."
---
# Allgemeine Geschäftsbedingungen (AGB)
## 1. Geltungsbereich
Diese Allgemeinen Geschäftsbedingungen gelten für alle Verträge, Lieferungen und sonstigen Leistungen der E-TIB GmbH gegenüber ihren Kunden.
## 2. Vertragsabschluss
Ein Vertrag kommt erst durch unsere schriftliche Auftragsbestätigung oder durch die Ausführung des Auftrags zustande.
## 3. Preise und Zahlungsbedingungen
Alle Preise verstehen sich in Euro zzgl. der gesetzlichen Mehrwertsteuer. Zahlungen sind innerhalb von 14 Tagen nach Rechnungsstellung ohne Abzug zu leisten.
## 4. Haftung
Wir haften für Schäden nur bei Vorsatz oder grober Fahrlässigkeit. Die Haftung für leichte Fahrlässigkeit ist ausgeschlossen, sofern keine wesentlichen Vertragspflichten verletzt wurden.
## 5. Gerichtsstand
Gerichtsstand für alle Streitigkeiten aus dem Vertragsverhältnis ist Cottbus, sofern der Kunde Kaufmann ist.

View File

@@ -0,0 +1,30 @@
---
title: "Moderne Verfahren im Kabeltiefbau"
date: "2024-03-15T10:00:00.000Z"
excerpt: "Erfahren Sie mehr über die neuesten Technologien und Verfahren im modernen Kabelleitungstiefbau, von Horizontalspülbohrungen bis hin zu grabenlosen Verlegetechniken."
featuredImage: "/assets/images/blog/tiefbau.jpg"
category: "Technik"
public: true
---
# Moderne Verfahren im Kabeltiefbau
Der Kabelleitungstiefbau hat sich in den letzten Jahren rasant entwickelt. Neue Technologien ermöglichen eine effizientere und umweltschonendere Verlegung von Versorgungsleitungen.
## Grabenlose Verlegetechniken
Eines der wichtigsten Verfahren ist die **Horizontalspülbohrung (HDD)**. Dieses Verfahren ermöglicht es, Leitungen unter Hindernissen wie Straßen, Flüssen oder Gebäuden zu verlegen, ohne die Oberfläche aufbrechen zu müssen.
### Vorteile der HDD-Bohrung:
- Minimale Beeinträchtigung des Verkehrs
- Schutz der Umwelt und vorhandener Vegetation
- Kürzere Bauzeiten
- Geringere Kosten bei komplexen Unterquerungen
## Klassischer Tiefbau in neuer Qualität
Auch der klassische Grabenbau profitiert von modernen Maschinen und präziser Planung. Durch den Einsatz von Saugbaggern und GPS-gestützter Vermessung können wir Schäden an Bestandsleitungen nahezu ausschließen.
## Fazit
Die Kombination aus bewährter Handwerkskunst und modernster Technik ist der Schlüssel zu erfolgreichen Infrastrukturprojekten. Wir bei E-TIB setzen konsequent auf Innovation, um unseren Kunden die bestmöglichen Ergebnisse zu liefern.

View File

@@ -0,0 +1,43 @@
---
title: "Datenschutzerklärung"
date: "2024-03-20"
excerpt: "Informationen zum Schutz Ihrer persönlichen Daten bei E-TIB."
---
# Datenschutzerklärung
## 1. Datenschutz auf einen Blick
### Allgemeine Hinweise
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können.
### Datenerfassung auf dieser Website
Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten können Sie dem Abschnitt „Hinweis zur Verantwortlichen Stelle“ in dieser Datenschutzerklärung entnehmen.
## 2. Hosting
Wir hosten unsere Website bei einem externen Dienstleister (Hoster). Die personenbezogenen Daten, die auf dieser Website erfasst werden, werden auf den Servern des Hosters gespeichert. Hierbei kann es sich v. a. um IP-Adressen, Kontaktanfragen, Meta- und Kommunikationsdaten, Webseitenzugriffe und sonstige Daten, die über eine Website generiert werden, handeln.
## 3. Allgemeine Hinweise und Pflichtinformationen
### Datenschutz
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.
### Hinweis zur Verantwortlichen Stelle
Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:
E-TIB GmbH
Gewerbestraße 22
03172 Guben
Telefon: +49 1520 7230518
E-Mail: d.joseph@e-tib.com
## 4. Datenerfassung auf dieser Website
### Kontaktformular
Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.
## 5. Analyse-Tools und Werbung
Wir nutzen auf dieser Website Umami Analytics. Umami ist eine datenschutzfreundliche Alternative zu Google Analytics, die keine persönlichen Daten sammelt und keine Cookies verwendet.

73
content/de/home.mdx Normal file
View File

@@ -0,0 +1,73 @@
---
title: Startseite
description: Willkommen bei E-TIB GmbH
---
<HomeHero
badge="Zukunftssichere Infrastruktur"
title="Energie & Kommunikation vernetzen"
description="Wir realisieren komplexe Versorgungsleitungen für eine moderne Gesellschaft. Verlässlich, innovativ und mit höchster Präzision im Kabelbau und der Bohrtechnik."
videoUrl="https://cdn.mintel.me/video/etib-hero.mp4"
linkText="Unsere Leistungen"
linkHref="/de/kompetenzen"
/>
<HomeSubCompanyTiles
badge="Unsere Unternehmen"
title="Die E-TIB Gruppe"
companies={[
{
title: "E-TIB GmbH",
description: "Ausführung elektrischer Infrastrukturprojekte",
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
},
{
title: "E-TIB Bohrtechnik GmbH",
description: "Präzise Horizontalbohrungen in allen Bodenklassen",
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
},
{
title: "E-TIB Verwaltung GmbH",
description: "Zentrale Dienste, Einkauf, Finanzen",
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
},
{
title: "E-TIB Ingenieurgesellschaft mbH",
description: "Planung, Projektierung, Dokumentation",
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
}
]}
/>
<HomeCompetenceBentoGrid
badge="Unsere Kompetenzen"
title="Alles aus einer Hand"
ctaLabel="Alle Leistungen"
ctaHref="/de/kompetenzen"
items={[
{
title: "Kabelbau",
description: "Verlegung von Mittel- und Niederspannungskabeln",
tag: "Energie",
size: "large"
},
{
title: "Bohrtechnik",
description: "Horizontalspülbohrverfahren für grabenlose Verlegung",
tag: "Innovation",
size: "medium"
},
{
title: "Planung",
description: "Trassenplanung und Genehmigungsverfahren",
tag: "Präzision",
size: "small"
},
{
title: "Vermessung",
description: "Digitale Bestandsdokumentation",
tag: "Digital",
size: "accent"
}
]}
/>

47
content/de/impressum.mdx Normal file
View File

@@ -0,0 +1,47 @@
---
title: "Impressum"
date: "2024-03-20"
excerpt: "Rechtliche Informationen zur E-TIB GmbH."
---
# Impressum
## Angaben gemäß § 5 TMG
E-TIB GmbH
Gewerbestraße 22
03172 Guben
**Vertreten durch:**
Danny Joseph
## Kontakt
Telefon: +49 1520 7230518
E-Mail: d.joseph@e-tib.com
## Registereintrag
Eintragung im Handelsregister.
Registergericht: Amtsgericht Cottbus
Registernummer: HRB 14781 CB
## Umsatzsteuer-ID
Umsatzsteuer-Identifikationsnummer gemäß § 27 a Umsatzsteuergesetz:
DE332345678 (Beispiel)
## Redaktionell verantwortlich
Danny Joseph
Gewerbestraße 22
03172 Guben
## EU-Streitschlichtung
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: [https://ec.europa.eu/consumers/odr/](https://ec.europa.eu/consumers/odr/).
Unsere E-Mail-Adresse finden Sie oben im Impressum.
## Verbraucherstreitbeilegung/Universalschlichtungsstelle
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.

35
content/de/karriere.mdx Normal file
View File

@@ -0,0 +1,35 @@
---
title: "Karriere bei E-TIB"
date: "2024-03-20"
excerpt: "Werden Sie Teil eines dynamischen Teams und bauen Sie mit uns die Infrastruktur der Zukunft."
---
# Karriere
Suchen Sie eine neue Herausforderung in einem zukunftssicheren Markt? Bei **E-TIB** arbeiten wir täglich daran, Deutschland zu vernetzen und die Energiewende voranzutreiben.
## Warum E-TIB?
Wir bieten mehr als nur einen Job. Bei uns erwartet Sie:
- **Sicherheit**: Ein unbefristeter Arbeitsvertrag in einer wachsenden Branche.
- **Entwicklung**: Regelmäßige Schulungen und Aufstiegsmöglichkeiten.
- **Teamgeist**: Flache Hierarchien und ein kollegiales Umfeld.
- **Ausstattung**: Modernste Maschinen und hochwertige Arbeitskleidung.
## Offene Stellen
Wir suchen aktuell Verstärkung in folgenden Bereichen:
- **Tiefbaufacharbeiter (m/w/d)**
- **Maschinisten für HDD-Bohrgeräte (m/w/d)**
- **Elektromonteure (m/w/d)**
- **Bauleiter Kabeltiefbau (m/w/d)**
## Initiativbewerbung
Nichts Passendes dabei? Wir freuen uns auch über Initiativbewerbungen von motivierten Talenten.
## Jetzt bewerben
Senden Sie Ihre Unterlagen einfach per E-Mail an **karriere@e-tib.com** oder nutzen Sie unser Kontaktformular. Wir freuen uns darauf, Sie kennenzulernen!

View File

@@ -0,0 +1,38 @@
---
title: "Unsere Kompetenzen"
date: "2024-03-20"
excerpt: "Von Kabelbau bis Elektromontage: Wir bieten ganzheitliche Lösungen für moderne Netzinfrastrukturen."
---
# Kompetenzen
Als Full-Service-Partner im Bereich Kabelleitungstiefbau decken wir das gesamte Spektrum moderner Infrastrukturprojekte ab.
## Kabelleitungstiefbau & Montage
Wir verlegen Kabel- und Rohrsysteme für alle Spannungsbereiche und Medientypen.
- **Kabelbau**: Klassischer Grabenbau und Verlegung.
- **Kabelpflugarbeiten**: Schnelle und effiziente Verlegung in geeignetem Gelände.
- **Glasfaser**: Ausbau von Breitbandnetzen (FTTX).
- **Elektromontage**: Montage und Anschluss von Kabeln bis 110 kV.
## Bohrtechnik (HDD)
Mit unseren gesteuerten Horizontalspülbohrungen unterqueren wir Hindernisse präzise und oberflächenschonend.
- **Präzision**: Punktgenaue Zielbohrungen auch über lange Distanzen.
- **Vielseitigkeit**: Einsatz in allen Bodenklassen.
- **Effizienz**: Minimale Baustelleneinrichtung und schnelle Durchführung.
## Planung & Dokumentation
Ein Projekt ist erst abgeschlossen, wenn die Dokumentation perfekt ist.
- **Trassenplanung**: Optimale Wegführung unter Berücksichtigung aller Randbedingungen.
- **Genehmigungsverfahren**: Wir kümmern uns um den Papierkram.
- **GIS-Dokumentation**: Digitale Erfassung der verlegten Infrastruktur.
## Bereit für Ihr Projekt?
Kontaktieren Sie uns für eine unverbindliche Beratung zu Ihrem Vorhaben.

34
content/de/ueber-uns.mdx Normal file
View File

@@ -0,0 +1,34 @@
---
title: "Über E-TIB"
date: "2024-03-20"
excerpt: "Die E-TIB Gruppe ist Ihr kompetenter Partner für Kabeltiefbau, Bohrtechnik und Ingenieurdienstleistungen."
---
# Über uns
Die **E-TIB Gruppe** steht für höchste Qualität und Zuverlässigkeit im Bereich der Energie- und Dateninfrastruktur. Mit unserem Hauptsitz in Guben und Projekten im gesamten Bundesgebiet sind wir ein wichtiger Akteur bei der Umsetzung der Energiewende.
## Unsere Philosophie
Wir glauben daran, dass eine stabile Infrastruktur das Fundament für eine erfolgreiche Zukunft ist. Deshalb setzen wir auf:
- **Präzision**: Exakte Planung und Ausführung sind für uns selbstverständlich.
- **Innovation**: Wir nutzen modernste Technik wie Horizontalspülbohrungen, um Projekte effizienter zu gestalten.
- **Nachhaltigkeit**: Unsere Verfahren sind darauf ausgelegt, die Umwelt so wenig wie möglich zu belasten.
## Die E-TIB Gruppe
Unsere Stärke liegt in der Bündelung spezialisierter Kompetenzen:
### E-TIB GmbH
Fokus auf die Ausführung elektrischer Infrastrukturprojekte und klassischen Kabeltiefbau.
### E-TIB Bohrtechnik GmbH
Spezialisten für grabenlose Verlegetechniken und gesteuerte Horizontalbohrungen.
### E-TIB Ingenieurgesellschaft mbH
Ihr Partner für Planung, Projektierung und die lückenlose Dokumentation komplexer Bauvorhaben.
## Kontaktieren Sie uns
Möchten Sie mehr über unsere Arbeit erfahren? Wir freuen uns auf das Gespräch mit Ihnen.

22
content/en/agb.mdx Normal file
View File

@@ -0,0 +1,22 @@
---
title: "Terms and Conditions"
date: "2024-03-20"
excerpt: "The General Terms and Conditions of E-TIB GmbH for our services and deliveries."
---
# Terms and Conditions (T&C)
## 1. Scope
These General Terms and Conditions apply to all contracts, deliveries, and other services provided by E-TIB GmbH to its customers.
## 2. Conclusion of Contract
A contract is only formed upon our written order confirmation or by the execution of the order.
## 3. Prices and Payment Terms
All prices are in Euro plus the statutory value-added tax. Payments are to be made within 14 days of the invoice date without any deduction.
## 4. Liability
We are liable for damages only in cases of intent or gross negligence. Liability for slight negligence is excluded, provided that no essential contractual obligations have been violated.
## 5. Jurisdiction
The place of jurisdiction for all disputes arising from the contractual relationship is Cottbus, provided the customer is a merchant.

View File

@@ -0,0 +1,30 @@
---
title: "Modern Methods in Cable Civil Engineering"
date: "2024-03-15T10:00:00.000Z"
excerpt: "Learn more about the latest technologies and methods in modern cable civil engineering, from horizontal directional drilling to trenchless installation techniques."
featuredImage: "/assets/images/blog/tiefbau.jpg"
category: "Technology"
public: true
---
# Modern Methods in Cable Civil Engineering
Cable civil engineering has evolved rapidly in recent years. New technologies enable more efficient and environmentally friendly installation of utility lines.
## Trenchless Installation Techniques
One of the most important methods is **Horizontal Directional Drilling (HDD)**. This method allows pipes to be laid under obstacles such as roads, rivers, or buildings without having to break open the surface.
### Benefits of HDD Drilling:
- Minimal impact on traffic
- Protection of the environment and existing vegetation
- Shorter construction times
- Lower costs for complex crossings
## Classic Civil Engineering in New Quality
Classic trench construction also benefits from modern machinery and precise planning. By using suction excavators and GPS-supported surveying, we can virtually eliminate damage to existing utility lines.
## Conclusion
The combination of proven craftsmanship and state-of-the-art technology is the key to successful infrastructure projects. At E-TIB, we consistently focus on innovation to deliver the best possible results for our customers.

View File

@@ -0,0 +1,44 @@
---
title: "Privacy Policy"
date: "2024-03-20"
excerpt: "Information on the protection of your personal data at E-TIB."
---
# Privacy Policy
## 1. Privacy at a Glance
### General Information
The following information provides a simple overview of what happens to your personal data when you visit this website. Personal data is any data that can be used to personally identify you.
### Data Collection on this Website
The data processing on this website is carried out by the website operator. You can find their contact details in the "Notice on the Responsible Party" section of this privacy policy.
## 2. Hosting
We host our website with an external service provider (hoster). The personal data collected on this website is stored on the hoster's servers. This may include IP addresses, contact requests, meta and communication data, website access, and other data generated via a website.
## 3. General Notes and Mandatory Information
### Data Protection
The operators of these pages take the protection of your personal data very seriously. We treat your personal data confidentially and in accordance with the statutory data protection regulations and this privacy policy.
### Notice on the Responsible Party
The responsible party for data processing on this website is:
E-TIB GmbH
Gewerbestraße 22
03172 Guben
Germany
Phone: +49 1520 7230518
Email: d.joseph@e-tib.com
## 4. Data Collection on this Website
### Contact Form
If you send us inquiries via the contact form, your details from the inquiry form, including the contact details you provided there, will be stored by us for the purpose of processing the inquiry and in case of follow-up questions. We do not pass on this data without your consent.
## 5. Analysis Tools and Advertising
We use Umami Analytics on this website. Umami is a privacy-friendly alternative to Google Analytics that does not collect personal data and does not use cookies.

73
content/en/home.mdx Normal file
View File

@@ -0,0 +1,73 @@
---
title: Home
description: Welcome to E-TIB GmbH
---
<HomeHero
badge="Future-Proof Infrastructure"
title="Networking Energy & Communication"
description="We realize complex supply lines for a modern society. Reliable, innovative and with the highest precision in cable construction and drilling technology."
videoUrl="https://cdn.mintel.me/video/etib-hero.mp4"
linkText="Our Services"
linkHref="/en/competencies"
/>
<HomeSubCompanyTiles
badge="Our Companies"
title="The E-TIB Group"
companies={[
{
title: "E-TIB GmbH",
description: "Execution of electrical infrastructure projects",
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
},
{
title: "E-TIB Bohrtechnik GmbH",
description: "Precise horizontal drilling in all soil classes",
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
},
{
title: "E-TIB Verwaltung GmbH",
description: "Central services, purchasing, finance",
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
},
{
title: "E-TIB Ingenieurgesellschaft mbH",
description: "Planning, project management, documentation",
icon: "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
}
]}
/>
<HomeCompetenceBentoGrid
badge="Our Competencies"
title="Everything from a single source"
ctaLabel="All Services"
ctaHref="/en/competencies"
items={[
{
title: "Cable Construction",
description: "Installation of medium and low voltage cables",
tag: "Energy",
size: "large"
},
{
title: "Drilling Technology",
description: "Horizontal directional drilling for trenchless installation",
tag: "Innovation",
size: "medium"
},
{
title: "Planning",
description: "Route planning and approval procedures",
tag: "Precision",
size: "small"
},
{
title: "Surveying",
description: "Digital as-built documentation",
tag: "Digital",
size: "accent"
}
]}
/>

49
content/en/impressum.mdx Normal file
View File

@@ -0,0 +1,49 @@
---
title: "Imprint"
date: "2024-03-20"
excerpt: "Legal information about E-TIB GmbH."
---
# Imprint
## Information according to § 5 TMG
E-TIB GmbH
Gewerbestraße 22
03172 Guben
Germany
**Represented by:**
Danny Joseph
## Contact
Phone: +49 1520 7230518
Email: d.joseph@e-tib.com
## Register Entry
Registration in the Commercial Register.
Registration Court: District Court Cottbus
Registration Number: HRB 14781 CB
## VAT ID
VAT identification number according to § 27 a VAT Act:
DE332345678 (Example)
## Responsible for Content
Danny Joseph
Gewerbestraße 22
03172 Guben
Germany
## EU Dispute Resolution
The European Commission provides a platform for online dispute resolution (ODR): [https://ec.europa.eu/consumers/odr/](https://ec.europa.eu/consumers/odr/).
Our email address can be found at the top of the imprint.
## Consumer Dispute Resolution/Universal Arbitration Board
We are not willing or obliged to participate in dispute resolution proceedings before a consumer arbitration board.

35
content/en/karriere.mdx Normal file
View File

@@ -0,0 +1,35 @@
---
title: "Careers at E-TIB"
date: "2024-03-20"
excerpt: "Become part of a dynamic team and help us build the infrastructure of the future."
---
# Careers
Are you looking for a new challenge in a future-proof market? At **E-TIB**, we work every day to connect Germany and drive the energy transition forward.
## Why E-TIB?
We offer more than just a job. With us, you can expect:
- **Security**: A permanent employment contract in a growing industry.
- **Development**: Regular training and promotion opportunities.
- **Team Spirit**: Flat hierarchies and a collegial environment.
- **Equipment**: State-of-the-art machinery and high-quality workwear.
## Open Positions
We are currently looking for reinforcement in the following areas:
- **Civil Engineering Workers (m/f/d)**
- **HDD Drill Rig Operators (m/f/d)**
- **Electrical Installers (m/f/d)**
- **Construction Managers for Cable Engineering (m/f/d)**
## Speculative Application
Nothing suitable? We also welcome speculative applications from motivated talents.
## Apply Now
Simply send your documents by email to **karriere@e-tib.com** or use our contact form. We look forward to meeting you!

View File

@@ -0,0 +1,38 @@
---
title: "Our Competencies"
date: "2024-03-20"
excerpt: "From cable construction to electrical installation: We provide holistic solutions for modern network infrastructures."
---
# Competencies
As a full-service partner in the field of cable civil engineering, we cover the entire spectrum of modern infrastructure projects.
## Cable Civil Engineering & Installation
We install cable and pipe systems for all voltage ranges and media types.
- **Cable Construction**: Classic trenching and installation.
- **Cable Plowing**: Fast and efficient installation in suitable terrain.
- **Fiber Optics**: Expansion of broadband networks (FTTX).
- **Electrical Installation**: Installation and connection of cables up to 110 kV.
## Drilling Technology (HDD)
With our controlled horizontal directional drilling, we cross obstacles precisely and with minimal surface impact.
- **Precision**: Pinpoint target drilling even over long distances.
- **Versatility**: Use in all soil classes.
- **Efficiency**: Minimal site setup and fast execution.
## Planning & Documentation
A project is only complete when the documentation is perfect.
- **Route Planning**: Optimal routing taking all boundary conditions into account.
- **Permit Procedures**: We take care of the paperwork.
- **GIS Documentation**: Digital recording of the installed infrastructure.
## Ready for Your Project?
Contact us for a non-binding consultation on your project.

34
content/en/ueber-uns.mdx Normal file
View File

@@ -0,0 +1,34 @@
---
title: "About E-TIB"
date: "2024-03-20"
excerpt: "The E-TIB Group is your competent partner for cable civil engineering, drilling technology, and engineering services."
---
# About Us
The **E-TIB Group** stands for the highest quality and reliability in the field of energy and data infrastructure. With our headquarters in Guben and projects throughout Germany, we are a key player in the implementation of the energy transition.
## Our Philosophy
We believe that a stable infrastructure is the foundation for a successful future. That's why we focus on:
- **Precision**: Exact planning and execution are a matter of course for us.
- **Innovation**: We use state-of-the-art technology such as horizontal directional drilling to make projects more efficient.
- **Sustainability**: Our methods are designed to have as little impact on the environment as possible.
## The E-TIB Group
Our strength lies in the pooling of specialized expertise:
### E-TIB GmbH
Focus on the execution of electrical infrastructure projects and classic cable civil engineering.
### E-TIB Bohrtechnik GmbH
Specialists in trenchless installation techniques and controlled horizontal drilling.
### E-TIB Ingenieurgesellschaft mbH
Your partner for planning, project management, and the seamless documentation of complex construction projects.
## Contact Us
Would you like to learn more about our work? We look forward to hearing from you.

View File

@@ -28,13 +28,13 @@ services:
- etib.localhost
env_file:
- ${ENV_FILE:-.env}
ports:
- "3010:3001"
environment:
NODE_ENV: development
# Force Garbage Collection before Docker kills the container (OOM)
NODE_OPTIONS: "--max-old-space-size=6144"
NEXT_TELEMETRY_DISABLED: "1"
POSTGRES_URI: postgres://${PAYLOAD_DB_USER:-payload}:${PAYLOAD_DB_PASSWORD:-120in09oenaoinsd9iaidon}@etib-db:5432/${PAYLOAD_DB_NAME:-payload}
PAYLOAD_SECRET: ${PAYLOAD_SECRET:-fallback-secret-for-dev}
UV_THREADPOOL_SIZE: "1"
RAYON_NUM_THREADS: "1"
NEXT_PRIVATE_WORKER_THREADS: "false"
@@ -63,7 +63,6 @@ services:
until curl -sf http://localhost:3001 > /dev/null; do sleep 2; done
echo '[warmup] Server is up! Pre-compiling routes...'
curl -sf http://localhost:3001/de > /dev/null 2>&1 && echo '[warmup] /de ready'
curl -sf http://localhost:3001/api/health/cms > /dev/null 2>&1 && echo '[warmup] /api/health/cms ready'
echo '[warmup] All routes pre-compiled ✓'
) &
pnpm next dev --webpack --hostname 0.0.0.0 --port 3001;
@@ -75,33 +74,6 @@ services:
- "traefik.http.services.${PROJECT_NAME:-klz}-app-svc.loadbalancer.server.port=3001"
- "traefik.docker.network=infra"
etib-db:
image: postgres:15-alpine
restart: unless-stopped
env_file:
- ${ENV_FILE:-.env}
environment:
POSTGRES_DB: ${PAYLOAD_DB_NAME:-payload}
POSTGRES_USER: ${PAYLOAD_DB_USER:-payload}
POSTGRES_PASSWORD: ${PAYLOAD_DB_PASSWORD:-120in09oenaoinsd9iaidon}
# Docker Desktop on Mac routes host connections via external IP, not 127.0.0.1.
# Without this, pg_hba.conf rejects the connection as "no encryption".
POSTGRES_HOST_AUTH_METHOD: trust
volumes:
- etib_db_data:/var/lib/postgresql/data
networks:
- default
ports:
- "54322:5432"
etib-redis:
image: redis:7-alpine
restart: unless-stopped
networks:
- default
ports:
- "16379:6379"
networks:
default:
name: ${PROJECT_NAME:-e-tib}-internal
@@ -109,8 +81,6 @@ networks:
external: true
volumes:
etib_db_data:
external: false
etib_node_modules:
etib_next_cache:
etib_turbo_cache:

View File

@@ -14,11 +14,9 @@ export default getRequestConfig(async ({ requestLocale }) => {
// console.debug(`[i18n] Fallback to "de" for locale: "${rawLocale}"`);
}
console.log(`[i18n] Resolved locale: ${locale}`);
let messages;
try {
messages = (await import(`../messages/${locale}.json`)).default;
console.log(`[i18n] Loaded messages for ${locale}`);
} catch (error) {
console.error(`[i18n] Error loading messages for ${locale}:`, error);
messages = {};

View File

@@ -1,14 +0,0 @@
{
"domain": "www.e-tib.com",
"crawledAt": "2026-02-25T13:37:58.849Z",
"totalPages": 7,
"urls": [
"https://www.e-tib.com",
"https://www.e-tib.com/index.html",
"https://www.e-tib.com/kompetenzen-01.html",
"https://www.e-tib.com/startseite.html",
"https://www.e-tib.com/ueber-uns.html",
"https://www.e-tib.com/kontakt.html",
"https://www.e-tib.com/impressum.html"
]
}

View File

@@ -1,253 +0,0 @@
{
"domain": "www.e-tib.com",
"crawledAt": "2026-02-25T14:52:16.278Z",
"totalPages": 6,
"navigation": [],
"existingFeatures": [
"search",
"forms",
"maps"
],
"services": [
"KOMPETENZEN",
"Kompetenzen",
"Kabelleitungstiefbau im Bereich     Mittelspannung/Niederspannung",
"Kabelleitungstiefbau im Bereich         Telekommunikation (Glasfaser)",
"Grabenlose Leitungsverlegung         Horizontalspülbohrungen, Erdrakete",
"Planung und Beratung im Bereich     Energie-/Telekommunikationsnetze",
"Vermessung und Dokumentation",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"companyInfo": {
"registerNumber": "HRB 12403Haftungsausschluss",
"phone": "+49 (0) 3561 / 68577",
"email": "d.joseph@e-tib.comWeb",
"address": "E-TIB GmbHGewerbestraße 22 D-03172 GubenTel",
"managingDirector": "Danny Joseph"
},
"pageInventory": [
{
"url": "https://www.e-tib.com/impressum.html",
"pathname": "/impressum.html",
"title": "Impressum",
"type": "legal",
"headings": [
"IMPRESSUM",
"IMPRESSUM",
"Impressum und Datenschutz",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"services": [],
"hasSearch": true,
"hasForms": true,
"hasMap": false,
"hasVideo": false,
"contentSummary": "UnternehmenKompetenzenReferenzenKarriereKontaktImpressum☰ Menü $z(document).ready(function(){ // we find the active page's a-tag and then walk upwards the dom marking the respective parent li as active/open $z('ul.zpmarkactive.default li a.active').parents('li').each(function(index){ var that = this; // do not add open class to immediate parent li if ( index > 0 ){ $z(that).addClass('open'); } $z(that).addClass('active'); //$z(that).find('> a').addClass('active'); }); $z(\"ul.zpmarkactive.default"
},
{
"url": "https://www.e-tib.com/index.html",
"pathname": "/index.html",
"title": "Herzlich willkommen!",
"type": "home",
"headings": [
"DIE EXPERTEN FÜR TIEFBAU",
"WILLKOMMEN BEI E-TIB",
"WILLKOMMEN BEI E-TIB",
"E-TIB - DIE EXPERTEN FÜR KABELTIEFBAU",
"Unternehmensgruppe",
"Hier finden Sie einen Überblick über alle Gesellschaften im Netzwerk der E-TIB",
"E-TIB GMBH",
"E-TIB Ingenieurgesellschaft mbH",
"NEMO GmbH",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"services": [],
"hasSearch": true,
"hasForms": true,
"hasMap": true,
"hasVideo": false,
"contentSummary": "DIE EXPERTEN FÜR TIEFBAU UnternehmenKompetenzenReferenzenKarriereKontaktImpressum☰ Menü $z(document).ready(function(){ // we find the active page's a-tag and then walk upwards the dom marking the respective parent li as active/open $z('ul.zpmarkactive.default li a.active').parents('li').each(function(index){ var that = this; // do not add open class to immediate parent li if ( index > 0 ){ $z(that).addClass('open'); } $z(that).addClass('active'); //$z(that).find('> a').addClass('active'); }); $z"
},
{
"url": "https://www.e-tib.com",
"pathname": "/",
"title": "Herzlich willkommen!",
"type": "home",
"headings": [
"DIE EXPERTEN FÜR TIEFBAU",
"WILLKOMMEN BEI E-TIB",
"WILLKOMMEN BEI E-TIB",
"E-TIB - DIE EXPERTEN FÜR KABELTIEFBAU",
"Unternehmensgruppe",
"Hier finden Sie einen Überblick über alle Gesellschaften im Netzwerk der E-TIB",
"E-TIB GMBH",
"E-TIB Ingenieurgesellschaft mbH",
"NEMO GmbH",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"services": [],
"hasSearch": true,
"hasForms": true,
"hasMap": true,
"hasVideo": false,
"contentSummary": "DIE EXPERTEN FÜR TIEFBAU UnternehmenKompetenzenReferenzenKarriereKontaktImpressum☰ Menü $z(document).ready(function(){ // we find the active page's a-tag and then walk upwards the dom marking the respective parent li as active/open $z('ul.zpmarkactive.default li a.active').parents('li').each(function(index){ var that = this; // do not add open class to immediate parent li if ( index > 0 ){ $z(that).addClass('open'); } $z(that).addClass('active'); //$z(that).find('> a').addClass('active'); }); $z"
},
{
"url": "https://www.e-tib.com/kompetenzen-01.html",
"pathname": "/kompetenzen-01.html",
"title": "Kompetenzen",
"type": "service",
"headings": [
"KOMPETENZEN",
"KOMPETENZEN",
"Kompetenzen",
"Kabelleitungstiefbau im Bereich     Mittelspannung/Niederspannung",
"Kabelleitungstiefbau im Bereich         Telekommunikation (Glasfaser)",
"Grabenlose Leitungsverlegung         Horizontalspülbohrungen, Erdrakete",
"Planung und Beratung im Bereich     Energie-/Telekommunikationsnetze",
"Vermessung und Dokumentation",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"services": [
"KOMPETENZEN",
"KOMPETENZEN",
"Kompetenzen",
"Kabelleitungstiefbau im Bereich     Mittelspannung/Niederspannung",
"Kabelleitungstiefbau im Bereich         Telekommunikation (Glasfaser)",
"Grabenlose Leitungsverlegung         Horizontalspülbohrungen, Erdrakete",
"Planung und Beratung im Bereich     Energie-/Telekommunikationsnetze",
"Vermessung und Dokumentation",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"hasSearch": true,
"hasForms": true,
"hasMap": false,
"hasVideo": false,
"contentSummary": "UnternehmenKompetenzenReferenzenKarriereKontaktImpressum☰ Menü $z(document).ready(function(){ // we find the active page's a-tag and then walk upwards the dom marking the respective parent li as active/open $z('ul.zpmarkactive.default li a.active').parents('li').each(function(index){ var that = this; // do not add open class to immediate parent li if ( index > 0 ){ $z(that).addClass('open'); } $z(that).addClass('active'); //$z(that).find('> a').addClass('active'); }); $z(\"ul.zpmarkactive.default"
},
{
"url": "https://www.e-tib.com/kontakt.html",
"pathname": "/kontakt.html",
"title": "Kontakt",
"type": "contact",
"headings": [
"KONTAKT",
"KONTAKT",
"Kontakt - Ansprechpartner, Zertifikate und Nachweise",
"Das sind Ihre Ansprechpartner",
"Danny Joseph",
"Dirk Schmidt",
"Oliver Fiedler",
"Martin Polan",
"Kerstin Joseph",
"Kathrin Lemanski"
],
"services": [],
"hasSearch": true,
"hasForms": true,
"hasMap": true,
"hasVideo": false,
"contentSummary": "UnternehmenKompetenzenReferenzenKarriereKontaktImpressum☰ Menü $z(document).ready(function(){ // we find the active page's a-tag and then walk upwards the dom marking the respective parent li as active/open $z('ul.zpmarkactive.default li a.active').parents('li').each(function(index){ var that = this; // do not add open class to immediate parent li if ( index > 0 ){ $z(that).addClass('open'); } $z(that).addClass('active'); //$z(that).find('> a').addClass('active'); }); $z(\"ul.zpmarkactive.default"
},
{
"url": "https://www.e-tib.com/startseite.html",
"pathname": "/startseite.html",
"title": "Herzlich willkommen!",
"type": "other",
"headings": [
"REFERENZEN",
"REFERENZEN",
"Referenzen",
"Breitbandausbau FTTC, Planungsregion Spreewald",
"Breitbandausbau FTTH, 29699 Bomlitz / 29690 Schwarmstedt",
"Neubau Kabeltrasse, 03149 Forst",
"Kabelverbindung U30 - T10, 15890 Eisenhüttenstadt",
"Einspeisung PV-Anlage 14728 Görne",
"Breitbandausbau FTTH, Landkreis Börde",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"services": [],
"hasSearch": true,
"hasForms": true,
"hasMap": false,
"hasVideo": false,
"contentSummary": "UnternehmenKompetenzenReferenzenKarriereKontaktImpressum☰ Menü $z(document).ready(function(){ // we find the active page's a-tag and then walk upwards the dom marking the respective parent li as active/open $z('ul.zpmarkactive.default li a.active').parents('li').each(function(index){ var that = this; // do not add open class to immediate parent li if ( index > 0 ){ $z(that).addClass('open'); } $z(that).addClass('active'); //$z(that).find('> a').addClass('active'); }); $z(\"ul.zpmarkactive.default"
},
{
"url": "https://www.e-tib.com/ueber-uns.html",
"pathname": "/ueber-uns.html",
"title": "Herzlich willkommen!",
"type": "about",
"headings": [
"KARRIERE",
"KARRIERE",
"KARRIERE BEI E-TIB",
"Wir suchen ab sofort in Festanstellung:",
"Initiativbewerbung (m/w/d)",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"services": [],
"hasSearch": true,
"hasForms": true,
"hasMap": false,
"hasVideo": false,
"contentSummary": "UnternehmenKompetenzenReferenzenKarriereKontaktImpressum☰ Menü $z(document).ready(function(){ // we find the active page's a-tag and then walk upwards the dom marking the respective parent li as active/open $z('ul.zpmarkactive.default li a.active').parents('li').each(function(index){ var that = this; // do not add open class to immediate parent li if ( index > 0 ){ $z(that).addClass('open'); } $z(that).addClass('active'); //$z(that).find('> a').addClass('active'); }); $z(\"ul.zpmarkactive.default"
}
],
"colors": [
"#fafafa",
"#117c61",
"#252",
"#333333"
],
"socialLinks": {},
"externalDomains": [
"etib-ing.com"
],
"images": [
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-2-von-100-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-34-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-37-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-273-von-80-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-18-von-99-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-17-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-3-von-99-large.jpg",
"https://www.e-tib.com/media/images/e-tib-logo-final.png",
"https://www.e-tib.com/media/images/etib_ing_logo-487w.png",
"https://www.e-tib.com/media/images/nemo-logo-final.png",
"https://www.e-tib.com/media/images/de_v_kofinanziert_von_der_europaeischen_union_pos.png",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-26-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-21-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-104-large.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-18.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-92.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-20.jpg",
"https://www.e-tib.com/media/images/nemo_erneuerbare-energien_solar_photovoltaik_windenergie_windkraft_tiefbau_netzanbindung_guben-45.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-15.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-34-von-100-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-268-von-80-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-31-large.jpg",
"https://www.e-tib.com/media/images/etib-ingenieure-tiefbau-solar-pv-planung-vermessung-gis-222-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-71-von-99-.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-28.jpg",
"https://www.e-tib.com/media/images/olli.jpg",
"https://www.e-tib.com/media/images/martin.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-32.jpg",
"https://www.e-tib.com/media/images/kathrin-lemanski_.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-235-von-80-.jpg",
"https://www.e-tib.com/media/images/maik_etib.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-26-von-91-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-113-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-50-large.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-63.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-63.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-60.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-17.jpg",
"https://www.e-tib.com/media/images/nemo_erneuerbare-energien_solar_photovoltaik_tiefbau_netzanbindung_guben_elektromobilitaet-18.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-91.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-51-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-19-von-99-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-24-von-99-large.jpg"
],
"employeeCount": null
}

View File

@@ -1,778 +0,0 @@
<!DOCTYPE html><html prefix="og: https://ogp.me/ns#" class="js responsivelayout supportsbreakout" lang="de" data-zpdebug="false" data-ptr="" data-pagename="Impressum"><head>
<script>
document.documentElement.className = document.documentElement.className.replace(/no-js/g, 'js');
</script>
<title>Impressum</title>
<meta charset="utf-8">
<meta name="description" content="">
<meta property="og:type" content="website">
<meta property="og:title" content="Impressum">
<meta property="og:description" content="">
<meta property="og:url" content="http://www.e-tib.com/impressum.html">
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:title" content="Impressum">
<meta property="twitter:description" content="">
<meta name="keywords" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Zeta Producer 16.4.0.0, ZP16X, 22.03.2022 12:05:28">
<meta name="robots" content="index, follow">
<link rel="preload" href="assets/fonts/fontawesome-webfont.woff2?v=4.7.0" as="font" type="font/woff2" crossorigin="">
<link class="zpfaviconmeta" rel="shortcut icon" type="image/x-icon" href="assets/etib_ohne.png?v=2021070601">
<link rel="stylesheet" href="assets/bundle.css?v=638011695645578054">
<link rel="stylesheet" href="assets/bundle-print.css?v=637853503328973638" media="print">
<style id="zple_container_104">
#container_104{background-color: #117C61 !important;}
</style>
<script src="assets/bundle.js?v=638011695645578054"></script>
<!-- Webfonts -->
<!-- Search Inline Script -->
<script>
function search1(){
if (document.forms.searchengine1.q.value.length == 0) {
alert("Bitte geben Sie einen Suchbegriff ein.");
return false;
}
}
</script>
<!-- RSS -->
</head>
<body id="flat-responsive" class="notouch hasfixednav hasheader js objectfit loaded">
<a id="top"></a>
<!-- Teaser "Banner" -->
<!-- Header -->
<div id="header">
<div id="nav">
<ul class="default submenu touchhovermenu clickhovermenu out" data-trianglemode="true"><li class="logo hidden" style="max-width: 1792px;"><a href="index.html"></a></li><li class="zpnodefaults"><a href="index.html" data-isredirected="false">Unternehmen</a></li><li class="zpnodefaults"><a href="kompetenzen-01.html" data-isredirected="false">Kompetenzen</a></li><li class="zpnodefaults"><a href="startseite.html" data-isredirected="false">Referenzen</a></li><li class="zpnodefaults"><a href="ueber-uns.html" data-isredirected="false">Karriere</a></li><li class="zpnodefaults"><a href="kontakt.html" data-isredirected="false">Kontakt</a></li><li class="zpnodefaults active"><a class="active" href="impressum.html" data-isredirected="false">Impressum</a></li><li class="overflownonly" style=""><a href="#" id="mobilenavtoggle" class="tabletonly toggle" aria-label="Menü ein-/ausblenden">☰ Menü</a></li></ul>
<script>
$z(document).ready(function(){
// we find the active page's a-tag and then walk upwards the dom marking the respective parent li as active/open
$z('ul.zpmarkactive.default li a.active').parents('li').each(function(index){
var that = this;
// do not add open class to immediate parent li
if ( index > 0 ){
$z(that).addClass('open');
}
$z(that).addClass('active');
//$z(that).find('> a').addClass('active');
});
$z("ul.zpmarkactive.default").removeClass("zpmarkactive");
/*
$z("ul.zpmarkactive.default li:has(a.active)").addClass("active");
$z("ul.zpmarkactive.default li.haschilds > a").addClass("haschilds");
$z("ul.zpmarkactive.default li.haschilds:has(a.active)").addClass("active");
$z("ul.zpmarkactive.default li.active > a").addClass("active");
$z("ul.zpmarkactive.default").removeClass("zpmarkactive")
*/
});
</script>
</div>
</div>
<div id="slider" class="noprint">
<a id="a1776"></a>
<div id="headerslider" class="flexslider inited">
<div class="flex-container" style="height: 0; padding-bottom: 34.8%;">
<div class="flex-viewport" style="overflow: hidden; position: relative;"><ul class="slides" style="width: 1000%; transition-duration: 0s; transform: translate3d(-1905px, 0px, 0px);"><li class="clone" aria-hidden="true" style="width: 1905px; margin-right: 0px; float: left; display: block;">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-2-von-100-large.jpg" width="2500" height="870" alt="" draggable="false">
</li>
<li style="width: 1905px; margin-right: 0px; float: left; display: block;" class="flex-active-slide" data-thumb-alt="">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-34-large.jpg" width="2500" height="870" alt="" draggable="false">
<div class="flex-caption">
<h2 style="">IMPRESSUM</h2>
</div>
</li>
<li data-thumb-alt="" style="width: 1905px; margin-right: 0px; float: left; display: block;">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-37-large.jpg" width="2500" height="870" alt="" draggable="false">
</li>
<li data-thumb-alt="" style="width: 1905px; margin-right: 0px; float: left; display: block;">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-2-von-100-large.jpg" width="2500" height="870" alt="" draggable="false">
</li>
<li style="width: 1905px; margin-right: 0px; float: left; display: block;" class="clone" aria-hidden="true">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-34-large.jpg" width="2500" height="870" alt="" draggable="false">
<div class="flex-caption">
<h2 style="">IMPRESSUM</h2>
</div>
</li></ul></div></div>
<ol class="flex-control-nav flex-control-paging"><li><a href="#" class="flex-active">1</a></li><li><a href="#">2</a></li><li><a href="#">3</a></li></ol><ul class="flex-direction-nav"><li class="flex-nav-prev"><a class="flex-prev" href="#">zurück</a></li><li class="flex-nav-next"><a class="flex-next" href="#">weiter</a></li></ul></div>
<script>
var autoplay = false;
function initSlider(){
$z('#headerslider .flex-container').flexslider({
useCSS: true,
touch: true,
keyboard: true,
itemMargin: 0,
animation: 'slide',
controlsContainer: '#headerslider.flexslider',
slideshow: autoplay,
slideshowSpeed: 7000,
directionNav: true,
controlNav: true,
randomize: false,
pauseOnHover: true,
smoothHeight: false,
prevText: "zur&#252;ck",
nextText: "weiter"
});
$z('#headerslider.flexslider').addClass("inited");
}
$z(document).ready(function() {
autoplay = true;
if ( $z("body").hasClass("zpreducemotion") ){
autoplay = false;
}
if ( $z('#headerslider .flex-container ul.slides li img').length > 0 ){
// init flexslider after 1st image has loaded, so have dimensions as quickly as possible without having to wait too long on window loaded or such and can start right with doc ready
$z('#headerslider .flex-container ul.slides li img').first().one("load", initSlider).each(function() {
if(this.complete) $z(this).trigger('load');
}); // .each() is to work around a browser bugs which don't fire load() for cached images http://stackoverflow.com/a/3877079
}
else{
// also init flexslider if it doesn't contain images and is text only
initSlider();
}
// activate links for individual slideshow
$z('#headerslider .flex-container ul.slides li[data-clickurl]').css("cursor","pointer");
$z('#headerslider .flex-container ul.slides li[data-clickurl]').click(function (event){
// Because this script captures clicks for a container, it will also fire when a regular link inside the container
// is clicked. Therefore, we check below, if a regular link i.e. <a href="#"> was the target of the click.
// Depending on the browser used, event.target might not be available, but event.srcElement would…
var targ;
if (event.target){
targ = event.target;
}
else if (event.srcElement) {
targ = event.srcElement;
}
if (targ.nodeName == "A") {
// a regular link i.e. in a Caption was clicked. Return early, so the link's href doesn't get overridden
return;
}
if ( $z(this).data("clicktarget") ){
window.open($z(this).data("clickurl"), $z(this).data("clicktarget"));
}
else{
window.location = $z(this).data("clickurl");
}
event.preventDefault;
return false;
});
});
</script>
</div>
<div id="content" class="main">
<div class="zparea zpgrid" data-numcolumns="" data-areaname="Standard" data-pageid="579">
<div id="container_82" class="zpContainer first zpBreakout " data-zpeleel="container" data-zpleid="82" style="width: 1905px; padding-left: 0px; margin-left: calc(50% - 952.5px);">
<div class="zpRow standard " data-row-id="311">
<div class="zpColumn odd zpColumnWidth1 c12 first last" style="" data-column-id="349">
<div class="zpColumnItem">
<div id="a1666" data-article-id="1666" data-zpleid="1666" class="articlearea zpwText">
<h1 style="color: rgb(17, 124, 97); text-align:left;" data-zpfieldname="headline">
Impressum und Datenschutz
</h1>
<strong><span style="color: #333333;">Anbieterkennung nach § 5 TMG</span></strong><span style="color: #333333;">:<br>E-TIB GmbH<br>Gewerbestraße 22
<br>D-03172 Guben<br>Tel.: +49 (0) 3561 / 68577 33<br>Fax: +49 (0) 3561 / 68577 35<br>E-Mail: d.joseph@e-tib.com<br>Web: www.e-tib.com<br>&nbsp;</span><p><span style="color: #333333;"><strong>Geschäftsführer:</strong></span></p><span style="color: #333333;">
Danny Joseph<br><br><strong>Handelsregister-Eintrag:</strong><br>Registergericht: Amtsgericht Cottbus<br>Registernummer: HRB 12403<br><br><strong>Haftungsausschluss (Disclaimer):</strong><br>Haftung für Inhalte<br><br>Als
Dienstanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf
diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8
bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet,
übermittelte oder gespeicherte fremde Informationen zu überwachen oder
nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit
hinweisen. Verpflichtungen zur Entfernung oder Sperrung der Nutzung von
Informationen nach den allgemeinen Gesetzen bleiben hiervon unberührt.
Eine diesbezügliche Haftung ist jedoch erst ab dem Zeitpunkt der
Kenntnis einer konkreten Rechtsverletzung möglich. Bei Bekanntwerden von
entsprechenden Rechtsverletzungen werden wir diese Inhalte umgehend
entfernen.<br>Haftung für Links<br><br>Unser Angebot enthält Links zu
externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben.
Deshalb können wir für diese fremden Inhalte auch keine Gewähr
übernehmen. Für die Inhalte der verlinkten Seiten ist stets der
jeweilige Anbieter oder Betreiber der Seiten verantwortlich. Die
verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche
Rechtsverstöße überprüft. Rechtswidrige Inhalte waren zum Zeitpunkt der
Verlinkung nicht erkennbar. Eine permanente inhaltliche Kontrolle der
verlinkten Seiten ist jedoch ohne konkrete Anhaltspunkte einer
Rechtsverletzung nicht zumutbar. Bei Bekanntwerden von
Rechtsverletzungen werden wir derartige Links umgehend entfernen.<br>Urheberrecht<br><br>Die
durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen
Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung,
Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der
Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des
jeweiligen Autors bzw. Erstellers. Downloads und Kopien dieser Seite
sind nur für den privaten, nicht kommerziellen Gebrauch gestattet.
Soweit die Inhalte auf dieser Seite nicht vom Betreiber erstellt wurden,
werden die Urheberrechte Dritter beachtet. Insbesondere werden Inhalte
Dritter als solche gekennzeichnet. Sollten Sie trotzdem auf eine
Urheberrechtsverletzung aufmerksam werden, bitten wir um einen
entsprechenden Hinweis. Bei Bekanntwerden von Rechtsverletzungen werden
wir derartige Inhalte umgehend entfernen.<br> &nbsp;</span><p><span style="color: #333333;"><strong>Datenschutz</strong></span></p><p><span style="color: #333333;">Die Betreiber dieser Seiten nehmen den
Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre
personenbezogenen Daten vertraulich und entsprechend der gesetzlichen
Datenschutzvorschriften sowie dieser Datenschutzerklärung. Die Nutzung
unserer Webseite ist in der Regel ohne Angabe personenbezogener Daten
möglich. Soweit auf unseren Seiten personenbezogene Daten
(beispielsweise Name, Anschrift oder E-Mail-Adressen) erhoben werden,
erfolgt dies, soweit möglich, stets auf freiwilliger Basis. Diese Daten
werden ohne Ihre ausdrückliche Zustimmung nicht an Dritte weitergegeben.
Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei
der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein
lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht
möglich.</span></p><span style="color: #333333;">Quelle: http://www.e-recht24.de</span>
</div>
</div>
</div>
</div><!-- .zpRow -->
</div><!-- close container -->
<div id="container_164" class="zpContainer last " data-zpeleel="container" data-zpleid="164">
<div class="zpRow standard " data-row-id="473">
<div class="zpFakeColumn flexbreak"></div>
<div class="zpColumn odd zpColumnWidth1 c12 first last" style="clear: left;" data-column-id="563">
<div class="zpColumnItem">
<div id="a1905" data-article-id="1905" data-zpleid="1905" class="articlearea zpwText">
<h2 style="color: #117C61; text-align:center;" data-zpfieldname="headline">ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU</h2>
</div>
</div>
</div>
</div><!-- .zpRow -->
</div><!-- close container -->
</div> <!-- .zparea -->
</div>
<!-- Search -->
<div id="search" class="container">
<script>
function getParameterByNamezpsearchform(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.search);
if(results == null) {
return "";
}
else {
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
}
function encode_htmlzpsearchform(astring) { //helps prevent XSS e.g. when printing search strings
astring = typeof astring !== 'undefined' ? astring : "";
astring = astring.replace(/</g, "&lt;");
astring = astring.replace(/>/g, "&gt;");
astring = astring.replace(/"/g, "&quo" + "t;");
astring = astring.replace(/'/g, "&#x27;");
astring = astring.replace(/\//g, "&#x2F;");
astring = astring.replace(/\[\$/g, "");
astring = astring.replace(/\$\]/g, "");
return astring;
}
function searchzpsearchform() {
var formId = "zpsearchform";
if (document.getElementById(formId).q.value == 'Suchbegriff') {
return false;
}
else if ($z.trim(document.getElementById(formId).q.value).length === 0) {
alert("Bitte geben Sie einen Suchbegriff ein.");
return false;
}
else{
return;
}
}
</script>
<form id="zpsearchform" name="searchengine1" onsubmit="return searchzpsearchform()" action="suche.html" method="get" class="zpsearchform">
<input id="zpsearchfield" class="zpsearchfield typetext" type="text" name="q" title="Suche" placeholder="Suchbegriff" aria-label="Suchbegriff">
<input id="zpsearchbutton" class="zpsearchbutton" type="image" name="submit" src="assets/stock-images/magnifier.png" alt="Suchen" title="Suchen" aria-label="Suchen">
</form>
<script>
$(document).ready(function(){
// set form field value to searchstring
var tmpSearchstring = getParameterByNamezpsearchform("q");
if ( tmpSearchstring ){
// fill layout searchfield with searchsting
document.getElementById("zpsearchform").q.value = tmpSearchstring;
// fill search page searchfield with searchsting
document.getElementById("zpsearchengine").q2.value = tmpSearchstring;
// focus search page searchfield
document.getElementById("zpsearchengine").q2.focus();
}
});
</script>
</div>
<div id="afterfooter" class="container">
<div class="zparea zpgrid" data-numcolumns="" data-areaname="Footer" data-pageid="579">
<div id="container_104" class="zpContainer first last styled zpBreakout " data-zpeleel="container" data-zpleid="104" style="width: 1905px; padding-left: 0px; margin-left: calc(50% - 952.5px);">
<div class="zpRow standard " data-row-id="383">
<div class="zpColumn odd zpColumnWidth1 c12 first last" style="" data-column-id="445">
<div class="zpColumnItem">
<div id="a1761" data-article-id="1761" data-zpleid="1761" class="articlearea zpwText">
<p style="text-align: center;"><span style="color: rgb(254, 254, 254);">E-TIB GmbH<br>Gewerbestraße 22
<br>D-03172 Guben<br>Tel.: +49 (0)
3561 / 68577 33<br>Fax: +49 (0) 3561 / 68577 35<br>E-Mail: info@e-tib.com<br>Web:
www.e-tib.com</span></p>
</div>
</div>
</div>
</div><!-- .zpRow -->
</div><!-- close container -->
</div> <!-- .zparea -->
</div>
<div id="footer" class="container">
<div id="xmenu" class="">
<a class="xmenulink" href="index.html">Unternehmen</a>
<a class="xmenulink" href="kontakt.html">Kontakt</a>
<a class="xmenulink" href="impressum.html">Impressum und Datenschutz</a>
</div>
<div id="copyright">
© 2026 E-TIB
</div>
</div>
<div id="totop" class="container noprint">
<a href="#top" class="zpSO-Uplink-Flat zpnolayoutlinkstyles" title="Nach oben"><span class="zp-sronly">Nach oben</span></a>
</div>
<script>
function setoverflown(s){
var retcode = false;
if ( $z("#nav").is(":hover") || ! $z("#nav > ul > li").length ){
// mouse is over nav, which opens submenues and might cause the overflow
// OR empty nav - needed because for IE an empty #nav == overflown()
return false;
}
$z("#nav > ul").removeClass("overflown");
if ( $z("#nav:not(.on) > ul").overflown() ){
// nav is already overflown by 1st-Level Menu entries (too many entries)
$z("#nav > ul").addClass("overflown");
retcode = true;
}
else{
// on non touch devices, check if nav would be overflown horizontally if all submenues are visible (i.e. too many submenues expanding to the right)
$z("#nav > ul ul:not(.nocalcoverflow)").css("display", "block");
if ( $z("#nav:not(.on) > ul").overflown("x") ){
$z("#nav > ul").addClass("overflown");
retcode = true;
}
$z("#nav > ul ul:not(.nocalcoverflow)").css("display", "");
// if the previous check didn't already match, check if one of the submenus is higher than the available space in the window because the nav is fixed and wouldn't scroll
if ( !retcode && $z("#nav.fixed").length ){
$z("#nav.fixed > ul ul:not(.nocalcoverflow)").css("display", "block");
$z("#nav.fixed > ul ul:not(.nocalcoverflow)").each(function(){
var eltop = $z(this).offset().top - $(window).scrollTop(); // gets the effective top position relative to the window
if ( (eltop + $(this).innerHeight()) > $z(window).innerHeight() ){
// submenu doesn't fit on screen and would be cut off
$z("#nav.fixed > ul").addClass("overflown");
retcode = true;
$z("#nav.fixed > ul ul:not(.nocalcoverflow)").css("display", "");
// exit the each loop early
return false;
}
});
$z("#nav.fixed > ul ul:not(.nocalcoverflow)").css("display", "");
}
}
// see if we need to scale to the logo to fit besides the mobile menu-button
var availspace = $z("#nav > ul").width() - $z("#nav > ul > li.overflownonly").width();
$z("#nav > ul > li.logo").css("max-width", availspace + "px");
return retcode;
}
function stickyNav(topoffset){
// make navigations menu sticky when scrolling the menu past the top of the window
var scrolltop = $z("body").scrollTop() || (document.documentElement && document.documentElement.scrollTop) || 0;
if ( !$z("#nav ul ul:visible").length && scrolltop - topoffset >= 0){
$z("body").addClass("padded");
$z("#nav").addClass("fixed");
}
else{
$z("body").removeClass("padded");
$z("#nav").removeClass("fixed");
}
// with a little delay, check if the menu gets overflown (i.e. because we're also displaying the logo in the nav now
setTimeout(function(){setoverflown();}, 100);
}
// globals
var navOffset;
$z(document).ready(function(){
// smooth scroll the layout specific go to top link
$z("a.zpSO-Uplink-Flat").click(function(e){
e.preventDefault();
if ( $z(this.hash).length ){
$z('html,body').animate({
scrollTop: $z(this.hash).offset().top - parseInt($z("body").css("padding-top"))
}, 500);
window.location.hash = this.hash;
}
});
// switches the Mobile Navigation-Menu on/off, when "Menu" link is clicked.
$z("a#mobilenavtoggle").on("click", function(e){
e.preventDefault();
$z("#nav").toggleClass("on");
setoverflown();
});
setoverflown();
var resizeTimeout = null;
$z(window).resize(zpthrottle(100, function(event) {
var event = event || window.event;
if ( event && $z(event.target.nodeType).length == 0 ){// enable window.resize only for the window object (resizing elements might also trigger window.resize) which doesn't have a nodeType
setoverflown();
}
}));
if ( "false" == "false" && ("false" == "true" || "false" == "true") ){
navOffset = $z("#nav:not(.fixed)").offset().top;
setTimeout(function(){stickyNav(navOffset);}, 100);
}
});
$z(window).on('load', function(){
setTimeout(function(){setoverflown();}, 500);
var navheight = 0;
if ( "false" == "false" ){
navheight = $z("#nav").outerHeight();
}
// pull anchors below fixed header into visible area
if ( location.hash != "" && $z(':target').length ) {
$z('html,body').animate({scrollTop: $z(':target').offset().top - navheight - 10 }, 600);
}
$z("a[href*='#']:not([href='#'])").not(".zpNoScroll").click(function () {
var target = $z(this).attr("href");
target = target.substring(target.indexOf("#"));
if ( $z(target).length ){
$z('html,body').animate({scrollTop: $z(target).offset().top - navheight - 10 }, 600);
}
});
if ( "false" == "false" && ("false" == "true" || "false" == "true") ){
if ( $z("#nav:not(.fixed)").length ){
navOffset = $z("#nav:not(.fixed)").offset().top;
}
setTimeout(function(){stickyNav(navOffset);}, 500);
$z(window).scroll(zpthrottle(100, function(event) {
stickyNav(navOffset);
}));
}
});
</script>
<!-- {{{ -->
<!--
* Layout: Flat Responsive by StS
* $Id: default.html 2022-03-09 11:20:24 +0100 Stefan S 8c952fdc662aa6619ae8ff22707dc8c8284a3b37 $
-->
<!-- }}} -->
<div id="fancybox-tmp"></div><div id="fancybox-loading"><div></div></div><div id="fancybox-overlay"></div><div id="fancybox-wrap"><div id="fancybox-outer"><div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div><div id="fancybox-content"></div><a id="fancybox-close"></a><div id="fancybox-title"></div><a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span><span class="zp-sronly">Voriges Bild (prev. image)</span></a><a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span><span class="zp-sronly">Nächstes Bild (next image)</span></a></div></div></body></html>

View File

@@ -1,34 +0,0 @@
{
"url": "https://www.e-tib.com/impressum.html",
"pathname": "/impressum.html",
"title": "Impressum",
"type": "legal",
"headings": [
"IMPRESSUM",
"IMPRESSUM",
"Impressum und Datenschutz",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"navItems": [],
"features": [
"search",
"forms"
],
"links": [
"/index.html",
"/kompetenzen-01.html",
"/startseite.html",
"/ueber-uns.html",
"/kontakt.html",
"/impressum.html",
"/"
],
"images": [
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-2-von-100-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-34-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-37-large.jpg"
],
"meta": {
"ogTitle": "Impressum"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +0,0 @@
{
"url": "https://www.e-tib.com/index.html",
"pathname": "/index.html",
"title": "Herzlich willkommen!",
"type": "home",
"headings": [
"DIE EXPERTEN FÜR TIEFBAU",
"WILLKOMMEN BEI E-TIB",
"WILLKOMMEN BEI E-TIB",
"E-TIB - DIE EXPERTEN FÜR KABELTIEFBAU",
"Unternehmensgruppe",
"Hier finden Sie einen Überblick über alle Gesellschaften im Netzwerk der E-TIB",
"E-TIB GMBH",
"E-TIB Ingenieurgesellschaft mbH",
"NEMO GmbH",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"navItems": [],
"features": [
"search",
"forms",
"maps"
],
"links": [
"/index.html",
"/kompetenzen-01.html",
"/startseite.html",
"/ueber-uns.html",
"/kontakt.html",
"/impressum.html",
"/"
],
"images": [
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-273-von-80-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-18-von-99-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-17-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-3-von-99-large.jpg",
"https://www.e-tib.com/media/images/e-tib-logo-final.png",
"https://www.e-tib.com/media/images/etib_ing_logo-487w.png",
"https://www.e-tib.com/media/images/nemo-logo-final.png",
"https://www.e-tib.com/media/images/de_v_kofinanziert_von_der_europaeischen_union_pos.png"
],
"meta": {
"ogTitle": "Herzlich willkommen!"
}
}

View File

@@ -1,46 +0,0 @@
{
"url": "https://www.e-tib.com",
"pathname": "/",
"title": "Herzlich willkommen!",
"type": "home",
"headings": [
"DIE EXPERTEN FÜR TIEFBAU",
"WILLKOMMEN BEI E-TIB",
"WILLKOMMEN BEI E-TIB",
"E-TIB - DIE EXPERTEN FÜR KABELTIEFBAU",
"Unternehmensgruppe",
"Hier finden Sie einen Überblick über alle Gesellschaften im Netzwerk der E-TIB",
"E-TIB GMBH",
"E-TIB Ingenieurgesellschaft mbH",
"NEMO GmbH",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"navItems": [],
"features": [
"search",
"forms",
"maps"
],
"links": [
"/index.html",
"/kompetenzen-01.html",
"/startseite.html",
"/ueber-uns.html",
"/kontakt.html",
"/impressum.html",
"/"
],
"images": [
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-273-von-80-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-18-von-99-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-17-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-3-von-99-large.jpg",
"https://www.e-tib.com/media/images/e-tib-logo-final.png",
"https://www.e-tib.com/media/images/etib_ing_logo-487w.png",
"https://www.e-tib.com/media/images/nemo-logo-final.png",
"https://www.e-tib.com/media/images/de_v_kofinanziert_von_der_europaeischen_union_pos.png"
],
"meta": {
"ogTitle": "Herzlich willkommen!"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,44 +0,0 @@
{
"url": "https://www.e-tib.com/kompetenzen-01.html",
"pathname": "/kompetenzen-01.html",
"title": "Kompetenzen",
"type": "service",
"headings": [
"KOMPETENZEN",
"KOMPETENZEN",
"Kompetenzen",
"Kabelleitungstiefbau im Bereich     Mittelspannung/Niederspannung",
"Kabelleitungstiefbau im Bereich         Telekommunikation (Glasfaser)",
"Grabenlose Leitungsverlegung         Horizontalspülbohrungen, Erdrakete",
"Planung und Beratung im Bereich     Energie-/Telekommunikationsnetze",
"Vermessung und Dokumentation",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"navItems": [],
"features": [
"search",
"forms"
],
"links": [
"/index.html",
"/kompetenzen-01.html",
"/startseite.html",
"/ueber-uns.html",
"/kontakt.html",
"/impressum.html",
"/"
],
"images": [
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-26-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-21-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-104-large.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-18.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-92.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-20.jpg",
"https://www.e-tib.com/media/images/nemo_erneuerbare-energien_solar_photovoltaik_windenergie_windkraft_tiefbau_netzanbindung_guben-45.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-15.jpg"
],
"meta": {
"ogTitle": "Kompetenzen"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,58 +0,0 @@
{
"url": "https://www.e-tib.com/kontakt.html",
"pathname": "/kontakt.html",
"title": "Kontakt",
"type": "contact",
"headings": [
"KONTAKT",
"KONTAKT",
"Kontakt - Ansprechpartner, Zertifikate und Nachweise",
"Das sind Ihre Ansprechpartner",
"Danny Joseph",
"Dirk Schmidt",
"Oliver Fiedler",
"Martin Polan",
"Kerstin Joseph",
"Kathrin Lemanski",
"Sven Kirbs",
"Maik Weichert",
"ZERTIFIKATE",
"Zertifikate",
"Nachweise",
"Nachweise",
"Hier finden Sie uns",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"navItems": [],
"features": [
"search",
"forms",
"maps"
],
"links": [
"/index.html",
"/kompetenzen-01.html",
"/startseite.html",
"/ueber-uns.html",
"/kontakt.html",
"/impressum.html",
"/"
],
"images": [
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-34-von-100-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-268-von-80-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-31-large.jpg",
"https://www.e-tib.com/media/images/etib-ingenieure-tiefbau-solar-pv-planung-vermessung-gis-222-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-71-von-99-.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-28.jpg",
"https://www.e-tib.com/media/images/olli.jpg",
"https://www.e-tib.com/media/images/martin.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-32.jpg",
"https://www.e-tib.com/media/images/kathrin-lemanski_.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-235-von-80-.jpg",
"https://www.e-tib.com/media/images/maik_etib.jpg"
],
"meta": {
"ogTitle": "Kontakt"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +0,0 @@
{
"url": "https://www.e-tib.com/startseite.html",
"pathname": "/startseite.html",
"title": "Herzlich willkommen!",
"type": "other",
"headings": [
"REFERENZEN",
"REFERENZEN",
"Referenzen",
"Breitbandausbau FTTC, Planungsregion Spreewald",
"Breitbandausbau FTTH, 29699 Bomlitz / 29690 Schwarmstedt",
"Neubau Kabeltrasse, 03149 Forst",
"Kabelverbindung U30 - T10, 15890 Eisenhüttenstadt",
"Einspeisung PV-Anlage 14728 Görne",
"Breitbandausbau FTTH, Landkreis Börde",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"navItems": [],
"features": [
"search",
"forms"
],
"links": [
"/index.html",
"/kompetenzen-01.html",
"/startseite.html",
"/ueber-uns.html",
"/kontakt.html",
"/impressum.html",
"/"
],
"images": [
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-26-von-91-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-113-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-50-large.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-63.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-63.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-60.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-17.jpg",
"https://www.e-tib.com/media/images/nemo_erneuerbare-energien_solar_photovoltaik_tiefbau_netzanbindung_guben_elektromobilitaet-18.jpg",
"https://www.e-tib.com/media/images/e-tib-guben-tiefbauarbeiten-leitungsverlegung-elektromontagen-glasfaser-solar-91.jpg"
],
"meta": {
"ogTitle": "Herzlich willkommen!"
}
}

View File

@@ -1,908 +0,0 @@
<!DOCTYPE html><html prefix="og: https://ogp.me/ns#" class="js responsivelayout supportsbreakout" lang="de" data-zpdebug="false" data-ptr="" data-pagename="Karriere"><head>
<script>
document.documentElement.className = document.documentElement.className.replace(/no-js/g, 'js');
</script>
<title>Herzlich willkommen!</title>
<meta charset="utf-8">
<meta name="description" content="">
<meta property="og:type" content="website">
<meta property="og:title" content="Herzlich willkommen!">
<meta property="og:description" content="">
<meta property="og:url" content="http://www.e-tib.com/ueber-uns.html">
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:title" content="Herzlich willkommen!">
<meta property="twitter:description" content="">
<meta name="keywords" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Zeta Producer 16.4.0.0, ZP16X, 22.03.2022 12:05:28">
<meta name="robots" content="index, follow">
<link rel="preload" href="assets/fonts/fontawesome-webfont.woff2?v=4.7.0" as="font" type="font/woff2" crossorigin="">
<link class="zpfaviconmeta" rel="shortcut icon" type="image/x-icon" href="assets/etib_ohne.png?v=2021070601">
<link rel="stylesheet" href="assets/bundle.css?v=638011695645578054">
<link rel="stylesheet" href="assets/bundle-print.css?v=637853503328973638" media="print">
<style id="zple_container_167">
#container_167{background-image: url(media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-39-large.jpg) !important;background-repeat: no-repeat !important;background-size: cover !important;background-position: center center !important;background-attachment: fixed !important;}
</style><style id="zple_row_483">
.zpRow[data-row-id="483"], .zpRow:first-child[data-row-id="483"], .zpRow:last-child[data-row-id="483"]{margin-top: 7% !important;margin-bottom: 7% !important;}
</style><style id="zple_column_573">
div.zpRow .zpColumn[data-column-id='573']{background-color: rgba(17, 124, 97, 0.4);padding-top: 15px;padding-bottom: 15px;}
</style><style>
#a1887.zpwButton , #a1887.zpwButton p{text-align: center;}
#a1887.zpwButton a.button{border-style: solid; border-width: 0px !important;}
#a1887.zpwButton a.button{background: rgba(17, 124, 97, 0.4) !important;}
#a1887.zpwButton a.button{color: #FAFAFA !important;}
#a1887.zpwButton a.button:hover{background: #FAFAFA !important;}
#a1887.zpwButton a.button:hover{color: #117C61 !important;}
#a1887.zpwButton a.button:hover{font-weight: bold !important;}
</style><style id="zple_container_154">
#container_154{margin-top: 0% !important;margin-bottom: 0% !important;}
#container_154{background-image: url(media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-45-large.jpg) !important;background-repeat: no-repeat !important;background-size: cover !important;background-position: center center !important;background-attachment: fixed !important;}
</style><style id="zple_row_459">
.zpRow[data-row-id="459"], .zpRow:first-child[data-row-id="459"], .zpRow:last-child[data-row-id="459"]{margin-top: 7% !important;margin-bottom: 7% !important;}
</style><style id="zple_container_109">
#container_109{background-color: #117C61 !important;}
</style>
<script src="assets/bundle.js?v=638011695645578054"></script>
<!-- Webfonts -->
<!-- Search Inline Script -->
<script>
function search1(){
if (document.forms.searchengine1.q.value.length == 0) {
alert("Bitte geben Sie einen Suchbegriff ein.");
return false;
}
}
</script>
<!-- RSS -->
</head>
<body id="flat-responsive" class="notouch hasfixednav hasheader js objectfit loaded">
<a id="top"></a>
<!-- Teaser "Banner" -->
<!-- Header -->
<div id="header">
<div id="nav">
<ul class="default submenu touchhovermenu clickhovermenu out" data-trianglemode="true"><li class="logo hidden" style="max-width: 1792px;"><a href="index.html"></a></li><li class="zpnodefaults"><a href="index.html" data-isredirected="false">Unternehmen</a></li><li class="zpnodefaults"><a href="kompetenzen-01.html" data-isredirected="false">Kompetenzen</a></li><li class="zpnodefaults"><a href="startseite.html" data-isredirected="false">Referenzen</a></li><li class="zpnodefaults active"><a class="active" href="ueber-uns.html" data-isredirected="false">Karriere</a></li><li class="zpnodefaults"><a href="kontakt.html" data-isredirected="false">Kontakt</a></li><li class="zpnodefaults"><a href="impressum.html" data-isredirected="false">Impressum</a></li><li class="overflownonly" style=""><a href="#" id="mobilenavtoggle" class="tabletonly toggle" aria-label="Menü ein-/ausblenden">☰ Menü</a></li></ul>
<script>
$z(document).ready(function(){
// we find the active page's a-tag and then walk upwards the dom marking the respective parent li as active/open
$z('ul.zpmarkactive.default li a.active').parents('li').each(function(index){
var that = this;
// do not add open class to immediate parent li
if ( index > 0 ){
$z(that).addClass('open');
}
$z(that).addClass('active');
//$z(that).find('> a').addClass('active');
});
$z("ul.zpmarkactive.default").removeClass("zpmarkactive");
/*
$z("ul.zpmarkactive.default li:has(a.active)").addClass("active");
$z("ul.zpmarkactive.default li.haschilds > a").addClass("haschilds");
$z("ul.zpmarkactive.default li.haschilds:has(a.active)").addClass("active");
$z("ul.zpmarkactive.default li.active > a").addClass("active");
$z("ul.zpmarkactive.default").removeClass("zpmarkactive")
*/
});
</script>
</div>
</div>
<div id="slider" class="noprint">
<a id="a1764"></a>
<div id="headerslider" class="flexslider inited">
<div class="flex-container" style="height: 0; padding-bottom: 34.8%;">
<div class="flex-viewport" style="overflow: hidden; position: relative;"><ul class="slides" style="width: 1000%; transition-duration: 0s; transform: translate3d(-1905px, 0px, 0px);"><li class="clone" aria-hidden="true" style="width: 1905px; margin-right: 0px; float: left; display: block;">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-51-large.jpg" width="2500" height="870" alt="" draggable="false">
</li>
<li style="width: 1905px; margin-right: 0px; float: left; display: block;" class="flex-active-slide" data-thumb-alt="">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-19-von-99-large.jpg" width="2500" height="870" alt="" draggable="false">
<div class="flex-caption">
<h2 style="">KARRIERE</h2>
</div>
</li>
<li data-thumb-alt="" style="width: 1905px; margin-right: 0px; float: left; display: block;">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-24-von-99-large.jpg" width="2500" height="870" alt="" draggable="false">
</li>
<li data-thumb-alt="" style="width: 1905px; margin-right: 0px; float: left; display: block;">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-51-large.jpg" width="2500" height="870" alt="" draggable="false">
</li>
<li style="width: 1905px; margin-right: 0px; float: left; display: block;" class="clone" aria-hidden="true">
<img src="media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-19-von-99-large.jpg" width="2500" height="870" alt="" draggable="false">
<div class="flex-caption">
<h2 style="">KARRIERE</h2>
</div>
</li></ul></div></div>
<ol class="flex-control-nav flex-control-paging"><li><a href="#" class="flex-active">1</a></li><li><a href="#">2</a></li><li><a href="#">3</a></li></ol><ul class="flex-direction-nav"><li class="flex-nav-prev"><a class="flex-prev" href="#">zurück</a></li><li class="flex-nav-next"><a class="flex-next" href="#">weiter</a></li></ul></div>
<script>
var autoplay = false;
function initSlider(){
$z('#headerslider .flex-container').flexslider({
useCSS: true,
touch: true,
keyboard: true,
itemMargin: 0,
animation: 'slide',
controlsContainer: '#headerslider.flexslider',
slideshow: autoplay,
slideshowSpeed: 7000,
directionNav: true,
controlNav: true,
randomize: false,
pauseOnHover: true,
smoothHeight: false,
prevText: "zur&#252;ck",
nextText: "weiter"
});
$z('#headerslider.flexslider').addClass("inited");
}
$z(document).ready(function() {
autoplay = true;
if ( $z("body").hasClass("zpreducemotion") ){
autoplay = false;
}
if ( $z('#headerslider .flex-container ul.slides li img').length > 0 ){
// init flexslider after 1st image has loaded, so have dimensions as quickly as possible without having to wait too long on window loaded or such and can start right with doc ready
$z('#headerslider .flex-container ul.slides li img').first().one("load", initSlider).each(function() {
if(this.complete) $z(this).trigger('load');
}); // .each() is to work around a browser bugs which don't fire load() for cached images http://stackoverflow.com/a/3877079
}
else{
// also init flexslider if it doesn't contain images and is text only
initSlider();
}
// activate links for individual slideshow
$z('#headerslider .flex-container ul.slides li[data-clickurl]').css("cursor","pointer");
$z('#headerslider .flex-container ul.slides li[data-clickurl]').click(function (event){
// Because this script captures clicks for a container, it will also fire when a regular link inside the container
// is clicked. Therefore, we check below, if a regular link i.e. <a href="#"> was the target of the click.
// Depending on the browser used, event.target might not be available, but event.srcElement would…
var targ;
if (event.target){
targ = event.target;
}
else if (event.srcElement) {
targ = event.srcElement;
}
if (targ.nodeName == "A") {
// a regular link i.e. in a Caption was clicked. Return early, so the link's href doesn't get overridden
return;
}
if ( $z(this).data("clicktarget") ){
window.open($z(this).data("clickurl"), $z(this).data("clicktarget"));
}
else{
window.location = $z(this).data("clickurl");
}
event.preventDefault;
return false;
});
});
</script>
</div>
<div id="content" class="main">
<div class="zparea zpgrid" data-numcolumns="" data-areaname="Standard" data-pageid="588">
<div id="container_106" class="zpContainer first zpBreakout" data-zpeleel="container" data-zpleid="106" style="padding-left: 0px; margin-left: calc(50% - 952.5px); width: 1905px;">
<div class="zpRow standard " data-row-id="414">
<div class="zpColumn odd zpColumnWidth1 c12 first last" style="" data-column-id="489">
<div class="zpColumnItem">
<div id="a1811" data-article-id="1811" data-zpleid="1811" class="articlearea zpwText">
<h2 style="color: rgb(17, 124, 97); " data-zpfieldname="headline">
KARRIERE BEI E-TIB
</h2>
<p><span style="color: #333333;">Die E-TIB GmbH wurde 2015 aus der Motivation gegründet, hochwertige Leistungen für unsere Kunden sowie faire Entlohnung für unsere Mitarbeiter zu ermöglichen. Neben der Entlohnung steht für uns die Wertschätzung jedes einzelnen Mitarbeiters an oberster Stelle.</span></p><p><br></p><p><span style="color: #333333;">Wir bieten unseren Mitarbeitern:</span></p><ul><li>Einsatz bei regionalen und überregionalen Bauvorhaben</li><li>sicherer, unbefristeter Arbeitsvertrag in Vollzeit<br></li><li>entsprechende Einarbeitung in einem engagierten Team</li><li>leistungsbezogene und faire Vergütung</li><li>Auslöse bis 30,00 EUR pro Arbeitstag</li><li>31 Tage Urlaub sowie Urlaubsgeld</li><li>Arbeitskleidung (Engelbert-Strauss)</li><li>monatliche Zusatzzahlungen (persönliche MasterCard)</li><li>Zuzahlung im Rahmen der betrieblichen Altersvorsorge</li><li>individuelle Weiterbildungs- und Entwicklungsmöglichkeiten</li><li>Stellung Fahrzeug zur betrieblichen Nutzung und Fahrten zur Baustelle</li><li>familiäres Betriebsklima mit betrieblich organisierten Veranstaltungen<br></li></ul><p><br></p><p><span style="color: #333333;">Unsere erfolgreiche Unternehmensentwicklung ermöglicht uns kontinuierlich neue Arbeitskräfte einzustellen. Wir sind immer auf der
Suche nach engagierten und motivierten Mitarbeitern. Werden Sie auch ein Teil
unseres Teams. Schauen Sie sich unsere offenen Stellen an oder bewerben Sie sich initiativ.</span><br></p>
</div>
</div>
</div>
</div><!-- .zpRow -->
</div><!-- close container -->
<div id="container_167" class="zpContainer styled zpBreakout " data-zpeleel="container" data-zpleid="167" style="width: 1905px; padding-left: 0px; margin-left: calc(50% - 952.5px);">
<div class="zpRow standard " data-row-id="483">
<div class="zpColumn odd zpColumnWidth1 c6 styled first last" style="" data-column-id="573">
<div class="zpColumnItem">
<div id="a1915" data-article-id="1915" data-zpleid="1915" class="articlearea zpwText">
<h1 data-zpfieldname="headline">Wir suchen ab sofort in Festanstellung:</h1>
</div>
</div>
</div>
</div><!-- .zpRow -->
</div><!-- close container -->
<section id="container_117" class="zpContainer zpBreakout " data-zpeleel="container" data-zpleid="117" style="width: 1905px; padding-left: 0px; margin-left: calc(50% - 952.5px);">
<div class="zpRow standard " data-row-id="484">
<div class="zpColumn odd zpColumnWidth1 c12 first last" style="" data-column-id="574">
<div class="zpColumnItem">
<div id="a1916" data-article-id="1916" data-zpleid="1916" class="articlearea zpwText">
<h2 style="color: rgb(17, 124, 97); " data-zpfieldname="headline">Initiativbewerbung (m/w/d)</h2>
<p><span style="color: #333333;"></span><span style="color: #333333;">Sie können sich vorstellen, eine langfristige Herausforderung in unserem Unternehmen anzunehmen, finden aber kein passendes Stellenangebot?</span></p><p>Bewerben Sie sich initiativ schriftlich per Mail oder Post an:</p><p>E-TIB GmbH<br>Gewerbestraße 22
<br>D-03172
Guben<br>E-Mail:
info@e-tib.com</p><p>Für Rückfragen melden Sie sich gerne unter:&nbsp;</p><p>Tel.: +49 (0) 3561 / 68577 33</p>
</div>
</div>
</div>
</div><!-- .zpRow -->
</section><!-- close container -->
<div id="container_154" class="zpContainer styled zpBreakout " data-zpeleel="container" data-zpleid="154" style="width: 1905px; padding-left: 0px; margin-left: calc(50% - 952.5px);">
<div class="zpRow standard " data-row-id="459">
<div class="zpColumn odd zpColumnWidth1 c12 first last" style="" data-column-id="545">
<div class="zpColumnItem">
<div id="a1887" data-article-id="1887" data-zpleid="1887" class="articlearea zpwButton">
<p>
<a href="kontakt.html" class="button">
<span>KOMMUNIKATION</span>
</a>
</p>
</div>
</div>
</div>
</div><!-- .zpRow -->
</div><!-- close container -->
<div id="container_162" class="zpContainer last " data-zpeleel="container" data-zpleid="162">
<div class="zpRow standard " data-row-id="471">
<div class="zpFakeColumn flexbreak"></div>
<div class="zpColumn odd zpColumnWidth1 c12 first last" style="clear: left;" data-column-id="561">
<div class="zpColumnItem">
<div id="a1903" data-article-id="1903" data-zpleid="1903" class="articlearea zpwText">
<h2 style="color: #117C61; text-align:center;" data-zpfieldname="headline">ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU</h2>
</div>
</div>
</div>
</div><!-- .zpRow -->
</div><!-- close container -->
</div> <!-- .zparea -->
</div>
<!-- Search -->
<div id="search" class="container">
<script>
function getParameterByNamezpsearchform(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.search);
if(results == null) {
return "";
}
else {
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
}
function encode_htmlzpsearchform(astring) { //helps prevent XSS e.g. when printing search strings
astring = typeof astring !== 'undefined' ? astring : "";
astring = astring.replace(/</g, "&lt;");
astring = astring.replace(/>/g, "&gt;");
astring = astring.replace(/"/g, "&quo" + "t;");
astring = astring.replace(/'/g, "&#x27;");
astring = astring.replace(/\//g, "&#x2F;");
astring = astring.replace(/\[\$/g, "");
astring = astring.replace(/\$\]/g, "");
return astring;
}
function searchzpsearchform() {
var formId = "zpsearchform";
if (document.getElementById(formId).q.value == 'Suchbegriff') {
return false;
}
else if ($z.trim(document.getElementById(formId).q.value).length === 0) {
alert("Bitte geben Sie einen Suchbegriff ein.");
return false;
}
else{
return;
}
}
</script>
<form id="zpsearchform" name="searchengine1" onsubmit="return searchzpsearchform()" action="suche.html" method="get" class="zpsearchform">
<input id="zpsearchfield" class="zpsearchfield typetext" type="text" name="q" title="Suche" placeholder="Suchbegriff" aria-label="Suchbegriff">
<input id="zpsearchbutton" class="zpsearchbutton" type="image" name="submit" src="assets/stock-images/magnifier.png" alt="Suchen" title="Suchen" aria-label="Suchen">
</form>
<script>
$(document).ready(function(){
// set form field value to searchstring
var tmpSearchstring = getParameterByNamezpsearchform("q");
if ( tmpSearchstring ){
// fill layout searchfield with searchsting
document.getElementById("zpsearchform").q.value = tmpSearchstring;
// fill search page searchfield with searchsting
document.getElementById("zpsearchengine").q2.value = tmpSearchstring;
// focus search page searchfield
document.getElementById("zpsearchengine").q2.focus();
}
});
</script>
</div>
<div id="afterfooter" class="container">
<div class="zparea zpgrid" data-numcolumns="" data-areaname="Footer" data-pageid="588">
<div id="container_109" class="zpContainer first last styled zpBreakout " data-zpeleel="container" data-zpleid="109" style="width: 1905px; padding-left: 0px; margin-left: calc(50% - 952.5px);">
<div class="zpRow standard " data-row-id="390">
<div class="zpColumn odd zpColumnWidth1 c12 first last" style="" data-column-id="454">
<div class="zpColumnItem">
<div id="a1770" data-article-id="1770" data-zpleid="1770" class="articlearea zpwText">
<p style="text-align: center;"><span style="color: #fafafa;">E-TIB GmbH<br>Gewerbestraße 22
<br>D-03172
Guben<br>Tel.: +49 (0) 3561 / 68577 33<br>Fax: +49 (0) 3561 / 68577 35<br>E-Mail:
info@e-tib.com<br>Web: www.e-tib.com</span></p>
</div>
</div>
</div>
</div><!-- .zpRow -->
</div><!-- close container -->
</div> <!-- .zparea -->
</div>
<div id="footer" class="container">
<div id="xmenu" class="">
<a class="xmenulink" href="index.html">Unternehmen</a>
<a class="xmenulink" href="kontakt.html">Kontakt</a>
<a class="xmenulink" href="impressum.html">Impressum und Datenschutz</a>
</div>
<div id="copyright">
© 2026 E-TIB
</div>
</div>
<div id="totop" class="container noprint">
<a href="#top" class="zpSO-Uplink-Flat zpnolayoutlinkstyles" title="Nach oben"><span class="zp-sronly">Nach oben</span></a>
</div>
<script>
function setoverflown(s){
var retcode = false;
if ( $z("#nav").is(":hover") || ! $z("#nav > ul > li").length ){
// mouse is over nav, which opens submenues and might cause the overflow
// OR empty nav - needed because for IE an empty #nav == overflown()
return false;
}
$z("#nav > ul").removeClass("overflown");
if ( $z("#nav:not(.on) > ul").overflown() ){
// nav is already overflown by 1st-Level Menu entries (too many entries)
$z("#nav > ul").addClass("overflown");
retcode = true;
}
else{
// on non touch devices, check if nav would be overflown horizontally if all submenues are visible (i.e. too many submenues expanding to the right)
$z("#nav > ul ul:not(.nocalcoverflow)").css("display", "block");
if ( $z("#nav:not(.on) > ul").overflown("x") ){
$z("#nav > ul").addClass("overflown");
retcode = true;
}
$z("#nav > ul ul:not(.nocalcoverflow)").css("display", "");
// if the previous check didn't already match, check if one of the submenus is higher than the available space in the window because the nav is fixed and wouldn't scroll
if ( !retcode && $z("#nav.fixed").length ){
$z("#nav.fixed > ul ul:not(.nocalcoverflow)").css("display", "block");
$z("#nav.fixed > ul ul:not(.nocalcoverflow)").each(function(){
var eltop = $z(this).offset().top - $(window).scrollTop(); // gets the effective top position relative to the window
if ( (eltop + $(this).innerHeight()) > $z(window).innerHeight() ){
// submenu doesn't fit on screen and would be cut off
$z("#nav.fixed > ul").addClass("overflown");
retcode = true;
$z("#nav.fixed > ul ul:not(.nocalcoverflow)").css("display", "");
// exit the each loop early
return false;
}
});
$z("#nav.fixed > ul ul:not(.nocalcoverflow)").css("display", "");
}
}
// see if we need to scale to the logo to fit besides the mobile menu-button
var availspace = $z("#nav > ul").width() - $z("#nav > ul > li.overflownonly").width();
$z("#nav > ul > li.logo").css("max-width", availspace + "px");
return retcode;
}
function stickyNav(topoffset){
// make navigations menu sticky when scrolling the menu past the top of the window
var scrolltop = $z("body").scrollTop() || (document.documentElement && document.documentElement.scrollTop) || 0;
if ( !$z("#nav ul ul:visible").length && scrolltop - topoffset >= 0){
$z("body").addClass("padded");
$z("#nav").addClass("fixed");
}
else{
$z("body").removeClass("padded");
$z("#nav").removeClass("fixed");
}
// with a little delay, check if the menu gets overflown (i.e. because we're also displaying the logo in the nav now
setTimeout(function(){setoverflown();}, 100);
}
// globals
var navOffset;
$z(document).ready(function(){
// smooth scroll the layout specific go to top link
$z("a.zpSO-Uplink-Flat").click(function(e){
e.preventDefault();
if ( $z(this.hash).length ){
$z('html,body').animate({
scrollTop: $z(this.hash).offset().top - parseInt($z("body").css("padding-top"))
}, 500);
window.location.hash = this.hash;
}
});
// switches the Mobile Navigation-Menu on/off, when "Menu" link is clicked.
$z("a#mobilenavtoggle").on("click", function(e){
e.preventDefault();
$z("#nav").toggleClass("on");
setoverflown();
});
setoverflown();
var resizeTimeout = null;
$z(window).resize(zpthrottle(100, function(event) {
var event = event || window.event;
if ( event && $z(event.target.nodeType).length == 0 ){// enable window.resize only for the window object (resizing elements might also trigger window.resize) which doesn't have a nodeType
setoverflown();
}
}));
if ( "false" == "false" && ("false" == "true" || "false" == "true") ){
navOffset = $z("#nav:not(.fixed)").offset().top;
setTimeout(function(){stickyNav(navOffset);}, 100);
}
});
$z(window).on('load', function(){
setTimeout(function(){setoverflown();}, 500);
var navheight = 0;
if ( "false" == "false" ){
navheight = $z("#nav").outerHeight();
}
// pull anchors below fixed header into visible area
if ( location.hash != "" && $z(':target').length ) {
$z('html,body').animate({scrollTop: $z(':target').offset().top - navheight - 10 }, 600);
}
$z("a[href*='#']:not([href='#'])").not(".zpNoScroll").click(function () {
var target = $z(this).attr("href");
target = target.substring(target.indexOf("#"));
if ( $z(target).length ){
$z('html,body').animate({scrollTop: $z(target).offset().top - navheight - 10 }, 600);
}
});
if ( "false" == "false" && ("false" == "true" || "false" == "true") ){
if ( $z("#nav:not(.fixed)").length ){
navOffset = $z("#nav:not(.fixed)").offset().top;
}
setTimeout(function(){stickyNav(navOffset);}, 500);
$z(window).scroll(zpthrottle(100, function(event) {
stickyNav(navOffset);
}));
}
});
</script>
<!-- {{{ -->
<!--
* Layout: Flat Responsive by StS
* $Id: default.html 2022-03-09 11:20:24 +0100 Stefan S 8c952fdc662aa6619ae8ff22707dc8c8284a3b37 $
-->
<!-- }}} -->
<div id="fancybox-tmp"></div><div id="fancybox-loading"><div></div></div><div id="fancybox-overlay"></div><div id="fancybox-wrap"><div id="fancybox-outer"><div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div><div id="fancybox-content"></div><a id="fancybox-close"></a><div id="fancybox-title"></div><a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span><span class="zp-sronly">Voriges Bild (prev. image)</span></a><a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span><span class="zp-sronly">Nächstes Bild (next image)</span></a></div></div></body></html>

View File

@@ -1,36 +0,0 @@
{
"url": "https://www.e-tib.com/ueber-uns.html",
"pathname": "/ueber-uns.html",
"title": "Herzlich willkommen!",
"type": "about",
"headings": [
"KARRIERE",
"KARRIERE",
"KARRIERE BEI E-TIB",
"Wir suchen ab sofort in Festanstellung:",
"Initiativbewerbung (m/w/d)",
"ENERGIE - TELEKOMMUNIKATION - INFRASTRUKTUR - BAU"
],
"navItems": [],
"features": [
"search",
"forms"
],
"links": [
"/index.html",
"/kompetenzen-01.html",
"/startseite.html",
"/ueber-uns.html",
"/kontakt.html",
"/impressum.html",
"/"
],
"images": [
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie-51-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-19-von-99-large.jpg",
"https://www.e-tib.com/media/images/etib_e-tib_tiefbau_guben_netzanbindung_energie_vermessung-24-von-99-large.jpg"
],
"meta": {
"ogTitle": "Herzlich willkommen!"
}
}

View File

@@ -1,31 +1,9 @@
import { getPayload } from 'payload';
import configPromise from '@payload-config';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { config } from '@/lib/config';
export function extractExcerpt(content: string): string {
if (!content) return '';
// Remove frontmatter if present (though matter() usually strips it out)
let text = content.replace(/^---[\s\S]*?---/, '');
// Remove MDX component imports and usages
text = text.replace(/<[^>]+>/g, '');
text = text.replace(/^[ \t]*import\s+.*$/gm, '');
text = text.replace(/^[ \t]*export\s+.*$/gm, '');
// Remove markdown headings
text = text.replace(/^#+.*$/gm, '');
// Extract first paragraph or combined lines
const paragraphs = text
.split(/\n\s*\n/)
.filter((p) => p.trim() && !p.trim().startsWith('---') && !p.trim().startsWith('#'));
if (paragraphs.length === 0) return '';
const excerpt = paragraphs[0]
.replace(/[*_`]/g, '') // remove markdown bold/italic/code
.replace(/\[(.*?)\]\(.*?\)/g, '$1') // replace links with their text
.replace(/\s+/g, ' ')
.trim();
return excerpt.length > 200 ? excerpt.slice(0, 197) + '...' : excerpt;
}
const BLOG_DIR = path.join(process.cwd(), 'content', 'blog');
export interface PostFrontmatter {
title: string;
@@ -41,149 +19,52 @@ export interface PostFrontmatter {
export interface PostData {
slug: string;
frontmatter: PostFrontmatter;
content: any; // Mapped to Lexical SerializedEditorState
content: string;
}
export function isPostVisible(post: { frontmatter: { date: string; public?: boolean } }) {
// If explicitly marked as not public, hide in production
if (post.frontmatter.public === false && config.isProduction) {
return false;
}
const postDate = new Date(post.frontmatter.date);
const now = new Date();
return !(postDate > now && config.isProduction);
}
export async function getPostBySlug(slug: string, locale: string): Promise<PostData | null> {
try {
const payload = await getPayload({ config: configPromise });
// Check locale-specific directory first
const localeDir = path.join(process.cwd(), 'content', locale, 'blog');
const filePath = path.join(localeDir, `${slug}.mdx`);
// First try: Find in the requested locale
let { docs } = await payload.find({
collection: 'posts',
where: {
slug: { equals: slug },
...(!config.showDrafts ? { _status: { equals: 'published' } } : {}),
},
locale: locale as any,
draft: config.showDrafts,
limit: 1,
});
if (!fs.existsSync(filePath)) return null;
// Fallback: If not found, try searching across all locales.
// This happens when a user uses the static language switcher
// e.g. switching from /en/blog/en-slug to /de/blog/en-slug.
if (!docs || docs.length === 0) {
const { docs: crossLocaleDocs } = await payload.find({
collection: 'posts',
where: {
slug: { equals: slug },
...(!config.showDrafts ? { _status: { equals: 'published' } } : {}),
},
locale: 'all',
draft: config.showDrafts,
limit: 1,
});
if (crossLocaleDocs && crossLocaleDocs.length > 0) {
// Fetch the found document again, but strictly in the requested locale
// so we get the correctly translated fields (like the localized slug)
const { docs: correctLocaleDocs } = await payload.find({
collection: 'posts',
where: {
id: { equals: crossLocaleDocs[0].id },
},
locale: locale as any,
draft: config.showDrafts,
limit: 1,
});
docs = correctLocaleDocs;
}
}
if (!docs || docs.length === 0) return null;
const doc = docs[0];
const source = fs.readFileSync(filePath, 'utf8');
const { content, data } = matter(source);
return {
slug: doc.slug,
frontmatter: {
title: doc.title,
date: doc.date,
excerpt: doc.excerpt || '',
category: doc.category || '',
featuredImage:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.url || doc.featuredImage.sizes?.card?.url
: null,
focalX:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalX
: 50,
focalY:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalY
: 50,
public: doc._status === 'published',
} as PostFrontmatter,
content: doc.content as any, // Native Lexical Editor State
slug,
frontmatter: data as PostFrontmatter,
content,
};
} catch (error) {
console.error(`[Payload] getPostBySlug failed for ${slug}:`, error);
return null;
}
}
export async function getAllPosts(locale: string): Promise<PostData[]> {
try {
const payload = await getPayload({ config: configPromise });
const { docs } = await payload.find({
collection: 'posts',
where: {
...(!config.showDrafts ? { _status: { equals: 'published' } } : {}),
},
locale: locale as any,
sort: '-date',
draft: config.showDrafts,
limit: 100,
});
const localeDir = path.join(process.cwd(), 'content', locale, 'blog');
if (!fs.existsSync(localeDir)) return [];
console.log(`[Payload] getAllPosts for ${locale}: Found ${docs.length} docs`);
const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.mdx'));
const posts = docs.map((doc) => {
return {
slug: doc.slug,
frontmatter: {
title: doc.title,
date: doc.date,
excerpt: doc.excerpt || '',
category: doc.category || '',
featuredImage:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.url || doc.featuredImage.sizes?.card?.url
: null,
focalX:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalX
: 50,
focalY:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalY
: 50,
} as PostFrontmatter,
// Pass the Lexical content object rather than raw markdown string
content: doc.content as any,
};
});
const posts = await Promise.all(
files.map(async f => {
const slug = f.replace('.mdx', '');
return await getPostBySlug(slug, locale);
})
);
// Integrity check: only show posts with a featured image in listings/sitemap
return posts.filter((p) => !!p.frontmatter.featuredImage);
} catch (error) {
console.error(`[Payload] getAllPosts failed for ${locale}:`, error);
return [];
}
return posts
.filter((p): p is PostData => p !== null)
.filter(isPostVisible)
.sort((a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime());
}
export async function getAllPostsMetadata(locale: string): Promise<Partial<PostData>[]> {
@@ -210,34 +91,10 @@ export async function getAdjacentPosts(
return { prev: null, next: null };
}
// Posts are sorted by date descending (newest first)
// So "next" post (newer) is at index - 1
// And "previous" post (older) is at index + 1
let next = currentIndex > 0 ? posts[currentIndex - 1] : null;
let prev = currentIndex < posts.length - 1 ? posts[currentIndex + 1] : null;
let isNextRandom = false;
let isPrevRandom = false;
const getRandomPost = (excludeSlugs: string[]) => {
const available = posts.filter((p) => !excludeSlugs.includes(p.slug));
if (available.length === 0) return null;
return available[Math.floor(Math.random() * available.length)];
};
// If there's no next post (we are at the newest post), show a random post instead
if (!next && posts.length > 2) {
next = getRandomPost([slug, prev?.slug].filter(Boolean) as string[]);
isNextRandom = true;
}
// If there's no previous post (we are at the oldest post), show a random post instead
if (!prev && posts.length > 2) {
prev = getRandomPost([slug, next?.slug].filter(Boolean) as string[]);
isPrevRandom = true;
}
return { prev, next, isPrevRandom, isNextRandom };
return { prev, next };
}
export function getReadingTime(content: string): number {
@@ -249,75 +106,18 @@ export function getReadingTime(content: string): number {
export function generateHeadingId(text: string): string {
let id = text.toLowerCase();
id = id.replace(/ä/g, 'ae');
id = id.replace(/ö/g, 'oe');
id = id.replace(/ü/g, 'ue');
id = id.replace(/ß/g, 'ss');
id = id.replace(/[*_`]/g, '');
id = id.replace(/[^\w\s-]/g, '');
id = id
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-+|-+$/g, '');
id = id.replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue').replace(/ß/g, 'ss');
id = id.replace(/[^\w\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '');
return id || 'heading';
}
export function getTextContent(node: any): string {
if (typeof node === 'string') return node;
if (typeof node === 'number') return node.toString();
if (Array.isArray(node)) return node.map(getTextContent).join('');
if (node && typeof node === 'object' && node.props && node.props.children) {
return getTextContent(node.props.children);
}
return '';
}
export function getHeadings(content: string): { id: string; text: string; level: number }[] {
const headingLines = content.split('\n').filter((line) => line.match(/^#{1,3}\s/));
const headingLines = content.split('\n').filter((line) => line.match(/^#{2,4}\s/));
return headingLines.map((line) => {
const level = (line.match(/^#+/)?.[0].length || 0) + 1; // Shift H1->H2, H2->H3, H3->H4
const rawText = line.replace(/^#+\s/, '').trim();
const cleanText = rawText.replace(/[*_`]/g, '');
const id = generateHeadingId(cleanText);
return { id, text: cleanText, level };
const level = line.match(/^#+/)?.[0].length || 2;
const text = line.replace(/^#+\s/, '').trim();
const id = generateHeadingId(text);
return { id, text, level };
});
}
export function extractLexicalHeadings(
node: any,
headings: { id: string; text: string; level: number }[] = [],
): { id: string; text: string; level: number }[] {
if (!node) return headings;
if (node.type === 'heading' && node.tag) {
const level = parseInt(node.tag.replace('h', ''));
const text = getTextContentFromLexical(node);
if (text) {
headings.push({
id: generateHeadingId(text),
text,
level,
});
}
}
if (node.children && Array.isArray(node.children)) {
node.children.forEach((child: any) => extractLexicalHeadings(child, headings));
}
return headings;
}
function getTextContentFromLexical(node: any): string {
if (node.type === 'text') {
return node.text || '';
}
if (node.children && Array.isArray(node.children)) {
return node.children.map(getTextContentFromLexical).join('');
}
return '';
}

21
lib/mdx.ts Normal file
View File

@@ -0,0 +1,21 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const CONTENT_DIR = path.join(process.cwd(), 'content');
export async function getMdxContent(locale: string, slug: string) {
const filePath = path.join(CONTENT_DIR, locale, `${slug}.mdx`);
if (!fs.existsSync(filePath)) {
return null;
}
const source = fs.readFileSync(filePath, 'utf8');
const { content, data } = matter(source);
return {
content,
frontmatter: data,
};
}

View File

@@ -1,193 +1,76 @@
import { getPayload } from 'payload';
import configPromise from '@payload-config';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { mapSlugToFileSlug } from './slugs';
import { config } from '@/lib/config';
const CONTENT_DIR = path.join(process.cwd(), 'content');
export interface PageFrontmatter {
title: string;
excerpt: string;
featuredImage: string | null;
excerpt?: string;
featuredImage?: string | null;
focalX?: number;
focalY?: number;
layout?: 'default' | 'fullBleed';
public?: boolean;
description?: string;
}
export interface PageData {
slug: string;
frontmatter: PageFrontmatter;
content: any; // Lexical AST Document
}
function mapDoc(doc: any): PageData {
return {
slug: doc.slug,
frontmatter: {
title: doc.title,
excerpt: doc.excerpt || '',
featuredImage:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.sizes?.card?.url || doc.featuredImage.url
: null,
focalX:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalX
: 50,
focalY:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalY
: 50,
layout: doc.layout || 'default',
} as PageFrontmatter,
content: doc.content as any,
};
content: string;
}
export async function getPageBySlug(slug: string, locale: string): Promise<PageData | null> {
// Guard against invalid locales hitting the DB (e.g. favicon.ico, assets)
if (locale !== 'de' && locale !== 'en') {
const fileSlug = await mapSlugToFileSlug(slug, locale);
const filePath = path.join(CONTENT_DIR, locale, `${fileSlug}.mdx`);
if (!fs.existsSync(filePath)) {
return null;
}
try {
const payload = await getPayload({ config: configPromise });
const fileSlug = await mapSlugToFileSlug(slug, locale);
// Try finding exact match first
let result = await payload.find({
collection: 'pages',
where: {
and: [
{ slug: { equals: fileSlug } },
...(!config.showDrafts ? [{ _status: { equals: 'published' } }] : []),
],
},
locale: locale as any,
depth: 1,
limit: 1,
});
// Fallback: search ALL locales
if (result.docs.length === 0) {
const crossResult = await payload.find({
collection: 'pages',
where: {
and: [
{ slug: { equals: fileSlug } },
...(!config.showDrafts ? [{ _status: { equals: 'published' } }] : []),
],
},
locale: 'all',
depth: 1,
limit: 1,
});
if (crossResult.docs.length > 0) {
// Fetch missing exact match by internal id
result = await payload.find({
collection: 'pages',
where: {
id: { equals: crossResult.docs[0].id },
},
locale: locale as any,
depth: 1,
limit: 1,
});
}
}
if (result.docs.length > 0) {
const doc = result.docs[0];
const source = fs.readFileSync(filePath, 'utf8');
const { content, data } = matter(source);
return {
slug: doc.slug,
frontmatter: {
title: doc.title,
excerpt: doc.excerpt || '',
featuredImage:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.sizes?.card?.url || doc.featuredImage.url
: null,
focalX:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalX
: 50,
focalY:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalY
: 50,
layout:
doc.layout === 'fullBleed' || doc.layout === 'default'
? doc.layout
: ('default' as const),
},
content: doc.content,
slug,
frontmatter: data as PageFrontmatter,
content,
};
}
return null;
} catch (error) {
console.error(`[Payload] getPageBySlug failed for ${slug}:`, error);
return null;
}
}
export async function getAllPages(locale: string): Promise<PageData[]> {
if (locale !== 'de' && locale !== 'en') {
return [];
const localeDir = path.join(CONTENT_DIR, locale);
if (!fs.existsSync(localeDir)) return [];
const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.mdx'));
const pages = await Promise.all(
files.map(async f => {
const slug = f.replace('.mdx', '');
return await getPageBySlug(slug, locale);
})
);
return pages.filter((p): p is PageData => p !== null);
}
try {
const payload = await getPayload({ config: configPromise });
export async function getAllPagesMetadata(locale: string): Promise<PageData[]> {
const localeDir = path.join(CONTENT_DIR, locale);
if (!fs.existsSync(localeDir)) return [];
const result = await payload.find({
collection: 'pages' as any,
locale: locale as any,
limit: 100,
const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.mdx'));
return files.map(f => {
const slug = f.replace('.mdx', '');
const filePath = path.join(localeDir, f);
const source = fs.readFileSync(filePath, 'utf8');
const { data } = matter(source);
return {
slug,
frontmatter: data as PageFrontmatter,
content: '', // Metadata only
};
});
return (result.docs as any[]).map(mapDoc);
} catch (error) {
console.error(`[Payload] getAllPages failed for ${locale}:`, error);
return [];
}
}
export async function getAllPagesMetadata(locale: string): Promise<Partial<PageData>[]> {
if (locale !== 'de' && locale !== 'en') {
return [];
}
try {
const payload = await getPayload({ config: configPromise });
const result = await payload.find({
collection: 'pages' as any,
locale: locale as any,
limit: 100,
});
return (result.docs as any[]).map((doc: any) => ({
slug: doc.slug,
frontmatter: {
title: doc.title,
excerpt: doc.excerpt || '',
featuredImage:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.sizes?.card?.url || doc.featuredImage.url
: null,
focalX:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalX
: 50,
focalY:
typeof doc.featuredImage === 'object' && doc.featuredImage !== null
? doc.featuredImage.focalY
: 50,
} as PageFrontmatter,
}));
} catch (error) {
console.error(`[Payload] getAllPagesMetadata failed for ${locale}:`, error);
return [];
}
}

View File

@@ -7,6 +7,7 @@
"karriere": "karriere",
"kompetenzen": "kompetenzen",
"ueber-uns": "ueber-uns",
"agb": "agb",
"start": "start"
},
"products": {},
@@ -87,21 +88,39 @@
},
"title": "Kontaktieren Sie uns",
"subtitle": "Haben Sie Fragen zu unseren Leistungen oder ein konkretes Projekt? Wir sind für Sie da.",
"heroSubtitle": "Wir sind für Sie da",
"info": {
"title": "Kontaktinformationen",
"subtitle": "Direkte Wege zu uns",
"howToReachUs": "So erreichen Sie uns",
"office": "Hauptsitz Guben",
"address": "Gewerbestraße 22\n03172 Guben\nDeutschland",
"phone": "+49 15207230518",
"email": "d.joseph@e-tib.com"
},
"hours": {
"title": "Öffnungszeiten",
"weekdays": "Mo. Fr.",
"weekdaysTime": "08:00 17:00 Uhr",
"weekend": "Sa. So.",
"closed": "Geschlossen"
},
"form": {
"title": "Nachricht senden",
"subtitle": "Senden Sie uns Ihr Anliegen",
"name": "Name",
"email": "E-Mail",
"emailPlaceholder": "ihre@email.de",
"message": "Nachricht",
"messagePlaceholder": "Ihre Nachricht an uns...",
"submit": "Senden",
"submitting": "Wird gesendet...",
"successTitle": "Vielen Dank!",
"successDesc": "Wir haben Ihre Nachricht erhalten und melden uns kurzfristig bei Ihnen."
"successDesc": "Wir haben Ihre Nachricht erhalten und melden uns kurzfristig bei Ihnen.",
"sendAnother": "Weitere Nachricht senden",
"errorTitle": "Fehler!",
"error": "Etwas ist schiefgelaufen. Bitte prüfen Sie Ihre Eingaben.",
"tryAgain": "Erneut versuchen"
}
},
"Competencies": {
@@ -133,7 +152,10 @@
},
"StandardPage": {
"badge": "Information",
"overview": "Übersicht"
"overview": "Übersicht",
"needHelp": "Brauchen Sie Unterstützung?",
"supportTeamAvailable": "Unser Expertenteam steht Ihnen für alle Fragen rund um Ihr Infrastrukturprojekt zur Verfügung.",
"contactUs": "Kontaktieren Sie uns"
},
"Error": {
"title": "Hoppla!",
@@ -142,5 +164,24 @@
"description": "Die gesuchte Seite konnte nicht gefunden werden.",
"cta": "Zur Startseite"
}
},
"Blog": {
"meta": {
"title": "Blog & Aktuelles",
"description": "Neuigkeiten und Einblicke in die Welt des Kabeltiefbaus und der Infrastruktur."
},
"featuredPost": "Top-Beitrag",
"readFullArticle": "Vollständigen Artikel lesen",
"latestNews": "Aktuelles",
"allArticles": "Alle Artikel",
"readMore": "Weiterlesen",
"prev": "Zurück",
"next": "Weiter",
"categories": {
"all": "Alle",
"industry": "Branche",
"technical": "Technik",
"sustainability": "Nachhaltigkeit"
}
}
}

View File

@@ -7,6 +7,7 @@
"career": "karriere",
"competencies": "kompetenzen",
"about-us": "ueber-uns",
"terms": "agb",
"start": "start"
},
"products": {},
@@ -87,21 +88,39 @@
},
"title": "Contact Us",
"subtitle": "Do you have questions about our services or a specific project? We are here for you.",
"heroSubtitle": "We are here for you",
"info": {
"title": "Contact Information",
"subtitle": "Direct ways to reach us",
"howToReachUs": "How to reach us",
"office": "Headquarters Guben",
"address": "Gewerbestraße 22\n03172 Guben\nGermany",
"phone": "+49 15207230518",
"email": "d.joseph@e-tib.com"
},
"hours": {
"title": "Opening Hours",
"weekdays": "Mon. Fri.",
"weekdaysTime": "08:00 17:00",
"weekend": "Sat. Sun.",
"closed": "Closed"
},
"form": {
"title": "Send a Message",
"subtitle": "Send us your inquiry",
"name": "Name",
"email": "Email",
"emailPlaceholder": "your@email.com",
"message": "Message",
"messagePlaceholder": "Your message to us...",
"submit": "Send",
"submitting": "Sending...",
"successTitle": "Thank you!",
"successDesc": "We have received your message and will get back to you shortly."
"successDesc": "We have received your message and will get back to you shortly.",
"sendAnother": "Send another message",
"errorTitle": "Error!",
"error": "Something went wrong. Please check your input and try again.",
"tryAgain": "Try Again"
}
},
"Competencies": {
@@ -133,7 +152,10 @@
},
"StandardPage": {
"badge": "Information",
"overview": "Overview"
"overview": "Overview",
"needHelp": "Need support?",
"supportTeamAvailable": "Our team of experts is available to answer all your questions regarding your infrastructure project.",
"contactUs": "Contact Us"
},
"Error": {
"title": "Oops!",
@@ -142,5 +164,24 @@
"description": "The requested page could not be found.",
"cta": "Back to Home"
}
},
"Blog": {
"meta": {
"title": "Blog & News",
"description": "Latest news and insights from the world of cable construction and infrastructure."
},
"featuredPost": "Featured Post",
"readFullArticle": "Read Full Article",
"latestNews": "Latest News",
"allArticles": "All Articles",
"readMore": "Read More",
"prev": "Prev",
"next": "Next",
"categories": {
"all": "All",
"industry": "Industry",
"technical": "Technical",
"sustainability": "Sustainability"
}
}
}

2
next-env.d.ts vendored
View File

@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -2,7 +2,7 @@ import withMintelConfig from '@mintel/next-config';
import withBundleAnalyzer from '@next/bundle-analyzer';
import { withSentryConfig } from '@sentry/nextjs';
import { withPayload } from '@payloadcms/next/withPayload';
import createMDX from '@next/mdx';
const isProd = process.env.NODE_ENV === 'production';
/** @type {import('next').NextConfig} */
@@ -20,7 +20,6 @@ const nextConfig = {
'framer-motion',
'@/components/ui',
'@sentry/nextjs',
'@payloadcms/richtext-lexical',
'react-hook-form',
'zod',
'date-fns',
@@ -523,7 +522,8 @@ const withAnalyzer = withBundleAnalyzer({
// withMintelConfig internally sets output:'standalone' and wraps with withSentryConfig.
// In dev mode, standalone triggers @vercel/nft file tracing on every compile through
// VirtioFS, which hammers CPU. Strip it for dev.
let resolvedConfig = withPayload(withAnalyzer(withMintelConfig(nextConfig)));
const withMDX = createMDX({});
let resolvedConfig = withMDX(withAnalyzer(withMintelConfig(nextConfig)));
if (!isProd) {
delete resolvedConfig.output;

View File

@@ -1 +1 @@
{"version":"4.1.2","results":[[":tests/navigation.e2e.test.ts",{"duration":12274.499042,"failed":false}]]}
{"version":"4.1.2","results":[[":tests/navigation.e2e.test.ts",{"duration":23819.145417,"failed":false}]]}

View File

@@ -8,11 +8,7 @@
"@mintel/next-config": "1.9.5",
"@mintel/next-feedback": "1.9.5",
"@mintel/next-utils": "1.9.5",
"@payloadcms/db-postgres": "^3.77.0",
"@payloadcms/email-nodemailer": "^3.77.0",
"@payloadcms/next": "^3.77.0",
"@payloadcms/richtext-lexical": "^3.77.0",
"@payloadcms/ui": "^3.77.0",
"@next/mdx": "^16.1.6",
"@react-email/components": "1.0.8",
"@react-pdf/renderer": "^4.3.2",
"@react-three/drei": "^10.7.7",
@@ -31,9 +27,8 @@
"leaflet": "^1.9.4",
"next": "16.1.6",
"next-i18next": "^15.4.3",
"next-mdx-remote": "^5.0.0",
"next-intl": "^4.8.2",
"nodemailer": "^7.0.12",
"payload": "^3.77.0",
"pdf-lib": "^1.17.1",
"pino": "^10.3.0",
"pino-pretty": "^13.1.3",
@@ -101,9 +96,9 @@
"xlsx-cli": "^1.1.3"
},
"scripts": {
"dev": "bash -c '[ -f .env ] || (cp .env.example .env && sed -i.bak \"s/TRAEFIK_HOST=e-tib.com/TRAEFIK_HOST=etib.localhost/\" .env && rm -f .env.bak && echo \"✅ Created .env from .env.example\"); trap \"COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml down && COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml up etib-app etib-db etib-proxy etib-redis --remove-orphans'",
"dev:local": "bash -c 'trap \"COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml down\" EXIT INT TERM; COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml up -d etib-db etib-proxy etib-redis && POSTGRES_URI=postgres://payload:120in09oenaoinsd9iaidon@localhost:54322/payload NODE_ENV=development next dev --webpack --port 3100 --hostname 0.0.0.0'",
"dev:infra": "COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml up -d etib-db etib-proxy",
"dev": "bash -c '[ -f .env ] || (cp .env.example .env && sed -i.bak \"s/TRAEFIK_HOST=e-tib.com/TRAEFIK_HOST=etib.localhost/\" .env && rm -f .env.bak && echo \"✅ Created .env from .env.example\"); trap \"COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml down && COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml up etib-app etib-proxy --remove-orphans'",
"dev:local": "next dev --webpack --port 3100 --hostname 0.0.0.0",
"dev:infra": "COMPOSE_PROJECT_NAME=etib-website docker-compose -f docker-compose.dev.yml up -d etib-proxy",
"build": "next build",
"start": "next start",
"lint": "eslint .",
@@ -128,9 +123,7 @@
"pdf:datasheets:legacy": "tsx ./scripts/generate-pdf-datasheets-pdf-lib.ts",
"excel:datasheets": "tsx ./scripts/generate-excel-datasheets.ts",
"brochure:generate": "tsx ./scripts/generate-brochure.ts",
"cms:migrate": "tsx ./node_modules/payload/bin.js migrate",
"cms:seed": "tsx ./scripts/seed-payload.ts",
"assets:push:testing": "bash ./scripts/assets-sync.sh local testing",
"brochure:generate": "tsx ./scripts/generate-brochure.ts",
"assets:push:staging": "bash ./scripts/assets-sync.sh local staging",
"assets:push:prod": "bash ./scripts/assets-sync.sh local prod",
"assets:pull:testing": "bash ./scripts/assets-sync.sh testing local",
@@ -143,11 +136,6 @@
"pagespeed:urls": "tsx -e \"import sitemap from './app/sitemap'; sitemap().then(urls => console.log(urls.map(u => u.url).join('\\n')))\"",
"backup:db": "bash ./scripts/backup-db.sh",
"restore:db": "bash ./scripts/restore-db.sh",
"cms:push:testing": "bash ./scripts/cms-sync.sh push testing",
"cms:push:staging": "bash ./scripts/cms-sync.sh push staging",
"cms:push:prod": "bash ./scripts/cms-sync.sh push prod",
"cms:pull:testing": "bash ./scripts/cms-sync.sh pull testing",
"cms:pull:staging": "bash ./scripts/cms-sync.sh pull staging",
"cms:pull:prod": "bash ./scripts/cms-sync.sh pull prod",
"prepare": "husky",
"preinstall": "npx only-allow pnpm"

File diff suppressed because it is too large Load Diff

View File

@@ -1,107 +0,0 @@
import { buildConfig } from 'payload';
import { postgresAdapter } from '@payloadcms/db-postgres';
import { lexicalEditor } from '@payloadcms/richtext-lexical';
import sharp from 'sharp';
import path from 'path';
import { fileURLToPath } from 'url';
import { nodemailerAdapter } from '@payloadcms/email-nodemailer';
import { BlocksFeature } from '@payloadcms/richtext-lexical';
import { payloadBlocks } from './src/payload/blocks/allBlocks';
import { migrations } from './src/migrations';
// Only disable sharp cache in production to prevent memory leaks.
// In dev, the cache avoids 41s+ re-processing per image through VirtioFS.
if (process.env.NODE_ENV === 'production') {
sharp.cache(false);
}
import { Users } from './src/payload/collections/Users';
import { Media } from './src/payload/collections/Media';
import { Posts } from './src/payload/collections/Posts';
import { FormSubmissions } from './src/payload/collections/FormSubmissions';
import { Pages } from './src/payload/collections/Pages';
import { Competences } from './src/payload/collections/Competences';
import { References } from './src/payload/collections/References';
import { Team } from './src/payload/collections/Team';
import { Jobs } from './src/payload/collections/Jobs';
import { Settings } from './src/payload/globals/Settings';
import { seedDatabase } from './src/payload/seed';
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
export default buildConfig({
onInit: async (payload) => {
// seedDatabase is removed from onInit to avoid running in every Next.js build worker
},
admin: {
user: Users.slug,
importMap: {
baseDir: path.resolve(dirname),
},
components: {
graphics: {
Logo: '/src/payload/components/Logo',
Icon: '/src/payload/components/Icon',
},
},
meta: {
titleSuffix: ' E-TIB',
icons: [{ rel: 'icon', type: 'image/x-icon', url: '/favicon.ico' }],
},
},
localization: {
locales: [
{ label: 'Deutsch', code: 'de' },
{ label: 'English', code: 'en' },
],
defaultLocale: 'de',
fallback: true,
},
collections: [Users, Media, Posts, FormSubmissions, Pages, Competences, References, Team, Jobs],
globals: [Settings],
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({
blocks: payloadBlocks,
}),
],
}),
secret: process.env.PAYLOAD_SECRET || 'etib-restoration-secret',
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
db: postgresAdapter({
migrationDir: null as any,
pool: {
connectionString:
process.env.DATABASE_URI ||
process.env.POSTGRES_URI ||
`postgresql://${process.env.PAYLOAD_DB_USER || 'payload'}:${process.env.PAYLOAD_DB_PASSWORD || '120in09oenaoinsd9iaidon'}@127.0.0.1:54322/${process.env.PAYLOAD_DB_NAME || 'payload'}`,
},
push: false,
}),
email: process.env.MAIL_HOST
? nodemailerAdapter({
defaultFromAddress:
process.env.MAIL_FROM?.replace(/.*<|>.*/g, '') || 'postmaster@mg.mintel.me',
defaultFromName: process.env.MAIL_FROM?.split('<')[0]?.trim() || 'E-TIB',
transportOptions: {
host: process.env.MAIL_HOST || 'smtp.eu.mailgun.org',
port: Number(process.env.MAIL_PORT) || 587,
...(process.env.MAIL_USERNAME
? {
auth: {
user: process.env.MAIL_USERNAME,
pass: process.env.MAIL_PASSWORD,
},
}
: {}),
},
})
: undefined,
sharp,
});

2820
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

110
proxy.ts
View File

@@ -1,110 +1,16 @@
import createMiddleware from 'next-intl/middleware';
import { NextRequest, NextResponse } from 'next/server';
// Create the internationalization middleware
const intlMiddleware = createMiddleware({
// A list of all locales that are supported
export default createMiddleware({
locales: ['en', 'de'],
// Used when no locale matches
defaultLocale: 'de',
localePrefix: 'always'
});
export default async function middleware(request: NextRequest) {
const { method, url, headers } = request;
const { pathname } = request.nextUrl;
// Explicit bypass for infrastructure, assets, and Payload CMS routes
// to avoid locale redirects/interception of static files.
if (
pathname.startsWith('/admin') ||
pathname.startsWith('/api') ||
pathname.startsWith('/assets') ||
pathname.startsWith('/stats') ||
pathname.startsWith('/errors') ||
pathname.startsWith('/health') ||
pathname.startsWith('/uploads') ||
pathname.includes('/api/og') ||
pathname.includes('opengraph-image') ||
pathname.includes('favicon.ico') ||
pathname.endsWith('sitemap.xml') ||
pathname.endsWith('manifest.webmanifest')
) {
return NextResponse.next();
}
// Build header object for logging
const headerObj: Record<string, string> = {};
headers.forEach((value, key) => {
headerObj[key] = value;
});
// Defensive URL correction for internal container leakage (0.0.0.0, etib-app, localhost)
// This prevents hydration mismatches and host poisoning in generated links/metadata.
const urlObj = new URL(url);
const internalHosts = ['0.0.0.0', 'etib-app', 'localhost', '127.0.0.1'];
let effectiveRequest = request;
if (internalHosts.includes(urlObj.hostname)) {
const proto = headers.get('x-forwarded-proto') || 'https';
// Prioritize x-forwarded-host (passed by Traefik) over the local Host header
const fallbackHost = process.env.NEXT_PUBLIC_BASE_URL
? new URL(process.env.NEXT_PUBLIC_BASE_URL).host
: 'e-tib.com';
const hostHeader = headers.get('x-forwarded-host') || headers.get('host') || fallbackHost;
urlObj.protocol = proto;
effectiveRequest = new NextRequest(urlObj, {
headers: request.headers,
});
if (process.env.NODE_ENV !== 'production' || !process.env.CI) {
console.log(
`🛡️ Proxy: Fixed internal URL leak: ${url} -> ${urlObj.toString()} | Proto: ${proto} | Host: ${hostHeader}`,
);
}
}
try {
// Apply internationalization middleware
const response = intlMiddleware(request);
console.log(`[Middleware] Resolved request to ${url} -> status: ${response.status}`);
// Upgrade 307 (Temporary Redirect) to 308 (Permanent Redirect)
// This improves compatibility with scanners (Website Carbon, PageSpeed) and SEO.
if (response.status === 307) {
const location = response.headers.get('Location');
if (location) {
const url = new URL(location, request.url);
return Response.redirect(url, 308);
}
}
// Allow iframe embedding from recorder domains
const referer = headers.get('referer') || '';
const recorderDomains = ['recorder.localhost', 'recorder.mintel.me'];
const isRecorderRequest = recorderDomains.some((domain) => referer.includes(domain));
if (isRecorderRequest) {
response.headers.delete('x-frame-options');
response.headers.delete('content-security-policy');
response.headers.set('Access-Control-Allow-Origin', '*');
}
return response;
} catch (error) {
console.error(
`Request failed: method=${method} url=${url} headers=${JSON.stringify(headerObj)}`,
error,
);
throw error;
}
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico|admin|manifest.webmanifest|.*\\.(?:svg|png|jpg|jpeg|gif|webp|pdf|xlsx|txt|vcf|xml|webm|mp4|map)$).*)',
'/(de|en)/:path*',
],
// Match all pathnames except for
// - /api (API routes)
// - /_next (Next.js internals)
// - /_static (inside /public)
// - all root files inside /public (e.g. /favicon.ico)
matcher: ['/((?!api|_next|assets|_static|_vercel|[\\w-]+\\.\\w+).*)']
};

View File

@@ -1,28 +0,0 @@
import { getPayload } from 'payload';
import configPromise from '../payload.config';
const clear = async () => {
const payload = await getPayload({ config: configPromise });
payload.logger.info('Clearing database...');
try {
const pages = await payload.find({ collection: 'pages', limit: 100 });
for (const page of pages.docs) await payload.delete({ collection: 'pages', id: page.id });
const jobs = await payload.find({ collection: 'jobs', limit: 100 });
for (const job of jobs.docs) await payload.delete({ collection: 'jobs', id: job.id });
const refs = await payload.find({ collection: 'references', limit: 100 });
for (const ref of refs.docs) await payload.delete({ collection: 'references', id: ref.id });
payload.logger.info('✅ Database clear completed successfully.');
process.exit(0);
} catch (err) {
payload.logger.error('❌ Database clear failed:');
payload.logger.error(err);
process.exit(1);
}
};
clear();

View File

@@ -1,257 +0,0 @@
import { getPayload } from 'payload';
import configPromise from '../payload.config';
const fixContent = async () => {
const payload = await getPayload({ config: configPromise });
payload.logger.info('Running complete content alignment script...');
const services = [
{ title: 'Kabelbau', enTitle: 'Cable Construction', description: 'Wir verlegen Kabel für sämtliche Spannungsebenen und übernehmen den kompletten Trassen- und Rohrleitungsbau.', tag: 'Tiefbau', size: 'large' },
{ title: 'Kabelpflugarbeiten', enTitle: 'Cable Plowing', description: 'Umweltschonendes und extrem effizientes Einpflügen von Kabeln und Rohren auf langen Strecken.', tag: 'Spezialmaschinen', size: 'medium' },
{ title: 'Horizontalspülbohrungen', enTitle: 'Directional Drilling', description: 'Grabenlose Leitungsverlegung unter Straßen, Flüssen und Bauwerken hindurch.', tag: 'Bohrtechnik', size: 'small' },
{ title: 'Elektromontagen bis 110 kV', enTitle: 'Electrical Installations', description: 'Installation, Montage und Wartung von Mittel- und Hochspannungsanlagen.', tag: 'Montage', size: 'medium' },
{ title: 'Glasfaser-Kabelmontagen', enTitle: 'Fiber Optic Montage', description: 'Spleißen, Messen und Einblasen von LWL-Kabeln für den modernen Breitbandausbau.', tag: 'Telekommunikation', size: 'small' },
{ title: 'Wartung & Störungsdienst', enTitle: 'Maintenance & Emergency', description: '24/7 Bereitschaft für schnelle Fehlerortung und Reparatur an Kabelanlagen.', tag: 'Service', size: 'accent' },
{ title: 'Genehmigungs- und Ausführungsplanung', enTitle: 'Planning & Permits', description: 'Einholen von Wegerechten und Erstellung baureifer Planungsunterlagen.', tag: 'Planung', size: 'small' },
{ title: 'Komplexe Querung', enTitle: 'Complex Crossings', description: 'Spezialtiefbau für die Unterquerung kritischer Infrastruktur (Bahn, Autobahn, Gewässer).', tag: 'Spezialtiefbau', size: 'large' },
{ title: 'Elektro- und Netzanschlussplanung', enTitle: 'Grid Connection Planning', description: 'Berechnung, Projektierung und Auslegung von Netzanschlüssen.', tag: 'Planung', size: 'medium' },
{ title: 'Vermessung & Dokumentation', enTitle: 'Surveying & Documentation', description: 'Digitale Trasseneinmessung (GPS/Tachymeter) und revisionssichere Dokumentation (GIS).', tag: 'Dokumentation', size: 'small' },
];
try {
for (const loc of ['de', 'en']) {
// 1. Home Page Alignment
const { docs: homeDocs } = await payload.find({
collection: 'pages',
where: { slug: { equals: 'home' } },
locale: loc as any,
limit: 1,
});
if (homeDocs.length > 0) {
payload.logger.info(`Aligning Home page content [${loc}]...`);
let content = homeDocs[0].content as any;
const children = content?.root?.children || [];
// Ensure Hero is correct
const heroIndex = children.findIndex((c: any) => c.fields?.blockType === 'heroVideo');
if (heroIndex >= 0) {
children[heroIndex].fields.title = loc === 'de' ? 'DIE EXPERTEN FÜR KABELTIEFBAU' : 'THE EXPERTS FOR CABLE CONSTRUCTION';
children[heroIndex].fields.subtitle = loc === 'de' ? 'Wir helfen beim Ausbau der Energiekabelnetze für eine grüne Zukunft.' : 'We help expanding the energy cable networks for a green future.';
children[heroIndex].fields.ctaLabel = loc === 'de' ? 'Jetzt anfragen' : 'Request now';
}
// Add/Update Competence Bento on Home
const bentoGridIndex = children.findIndex((c: any) => c.fields?.blockType === 'homeCompetenceBentoGrid');
const bentoBlock = {
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'homeCompetenceBentoGrid',
badge: loc === 'de' ? 'Leistungsspektrum' : 'Service Spectrum',
title: loc === 'de' ? 'Umfassende Lösungen für komplexe Netzwerke' : 'Comprehensive solutions for complex networks',
ctaLabel: loc === 'de' ? 'Alle Kompetenzen ansehen' : 'View all competencies',
ctaHref: loc === 'de' ? '/kompetenzen' : '/en/competencies',
items: services.map(s => ({
title: loc === 'de' ? s.title : s.enTitle,
description: s.description,
tag: s.tag,
size: s.size
})),
},
};
if (bentoGridIndex >= 0) {
children[bentoGridIndex] = bentoBlock;
} else {
children.splice(2, 0, bentoBlock);
}
await payload.update({
collection: 'pages',
id: homeDocs[0].id,
locale: loc as any,
data: {
title: 'Home',
content: { root: { ...content.root, children } }
},
});
}
// 2. Kompetenzen Page
const { docs: compDocs } = await payload.find({
collection: 'pages',
where: { slug: { equals: 'kompetenzen' } },
locale: loc as any,
limit: 1,
});
const compData = {
title: loc === 'de' ? 'Kompetenzen' : 'Competencies',
slug: 'kompetenzen',
_status: 'published',
content: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'homeCompetenceBentoGrid',
badge: loc === 'de' ? 'Leistungsspektrum' : 'Service Spectrum',
title: loc === 'de' ? 'Unsere Kompetenzen im Überblick' : 'Our Competencies at a glance',
items: services.map(s => ({
title: loc === 'de' ? s.title : s.enTitle,
description: s.description,
tag: s.tag,
size: s.size
})),
},
}
]
}
}
};
if (compDocs.length > 0) {
payload.logger.info(`Updating Kompetenzen page [${loc}]...`);
await payload.update({
collection: 'pages',
id: compDocs[0].id,
locale: loc as any,
data: compData,
});
} else {
payload.logger.info(`Creating Kompetenzen page [${loc}]...`);
await payload.create({
collection: 'pages',
locale: loc as any,
data: compData
});
}
// 3. Über uns (Timeline & Group Tiles)
const { docs: aboutDocs } = await payload.find({
collection: 'pages',
where: { slug: { equals: 'ueber-uns' } },
locale: loc as any,
limit: 1,
});
const aboutData = {
title: loc === 'de' ? 'Über uns' : 'About us',
slug: 'ueber-uns',
_status: 'published',
content: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'homeSubCompanyTiles',
title: loc === 'de' ? 'Die E-TIB Unternehmensgruppe' : 'The E-TIB Corporate Group',
companies: [
{ title: 'E-TIB GmbH', description: loc === 'de' ? 'Ausführung elektrischer Infrastrukturprojekte.' : 'Execution of electrical infrastructure projects.' },
{ title: 'E-TIB Bohrtechnik GmbH', description: loc === 'de' ? 'Präzise Horizontalbohrungen in allen Bodenklassen.' : 'Precise horizontal directional drilling.' },
{ title: 'E-TIB Ingenieurgesellschaft mbH', description: loc === 'de' ? 'Planung, Projektierung und Dokumentation.' : 'Planning, engineering and documentation.' },
{ title: 'E-TIB Verwaltung GmbH', description: loc === 'de' ? 'Zentrale Dienste, Einkauf und Finanzen.' : 'Central services, purchasing and finance.' },
]
}
},
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'companyTimeline',
title: loc === 'de' ? 'Unsere Geschichte' : 'Our History',
items: [
{ year: '2015', title: 'Gründung E-TIB GmbH', description: 'Start der Ausführung von Kabelbauprojekten in Guben.' },
{ year: '2019', title: 'Gründung Ingenieurgesellschaft', description: 'Erweiterung um Planung und Projektierung.' },
{ year: '2025', title: 'Erweiterung Bohrtechnik', description: 'Eigenständige Bohrtechnik-Sparte für Horizontalbohrungen.' },
]
}
}
]
}
}
};
if (aboutDocs.length > 0) {
payload.logger.info(`Updating Über uns page [${loc}]...`);
await payload.update({
collection: 'pages',
id: aboutDocs[0].id,
locale: loc as any,
data: aboutData,
});
} else {
payload.logger.info(`Creating Über uns page [${loc}]...`);
await payload.create({
collection: 'pages',
locale: loc as any,
data: aboutData
});
}
// 4. Impressum
const { docs: impDocs } = await payload.find({
collection: 'pages',
where: { slug: { equals: 'impressum' } },
locale: loc as any,
limit: 1,
});
const impData = {
title: loc === 'de' ? 'Impressum' : 'Imprint',
slug: 'impressum',
_status: 'published',
content: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
type: 'paragraph',
children: [{ type: 'text', text: 'E-TIB GmbH\nGewerbestraße 22\n03172 Guben\n\nGeschäftsführung: Danny Joseph\nAmtsgericht Cottbus, HRB 12403 CB\nUSt-ID: DE304799919', version: 1 }]
}
]
}
}
};
if (impDocs.length > 0) {
await payload.update({ collection: 'pages', id: impDocs[0].id, locale: loc as any, data: impData });
} else {
await payload.create({ collection: 'pages', locale: loc as any, data: impData });
}
}
payload.logger.info('✅ Content alignment completed successfully!');
process.exit(0);
} catch (err: any) {
payload.logger.error('❌ Content alignment failed!');
if (err.data) {
payload.logger.error(JSON.stringify(err.data, null, 2));
} else {
payload.logger.error(err.stack || err.message || err);
}
process.exit(1);
}
};
fixContent();

View File

@@ -1,104 +0,0 @@
import { getPayload } from 'payload';
import configPromise from '../payload.config';
const fixHome = async () => {
const payload = await getPayload({ config: configPromise });
payload.logger.info('Running missing Home page script...');
try {
for (const loc of ['de', 'en']) {
const existingHome = await payload.find({
collection: 'pages',
where: { slug: { equals: 'home' } },
locale: loc as any,
limit: 1,
});
const homeData = {
title: loc === 'de' ? 'Startseite' : 'Home',
slug: 'home',
_status: 'published',
content: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'homeHero',
title: loc === 'de' ? 'DIE EXPERTEN FÜR \nKABELTIEFBAU' : 'THE EXPERTS FOR \nCABLE ENGINEERING',
subtitle: loc === 'de' ? 'Wir verbinden Infrastruktur mit Präzision. Von Horizontalbohrungen bis zu komplexen Leitungsnetzen.' : 'We connect infrastructure with precision. From horizontal drilling to complex pipe networks.',
videoUrl: '/assets/dummy-hero.mp4',
ctaLabel: loc === 'de' ? 'Unternehmen entdecken' : 'Discover Company',
ctaHref: '#unternehmen',
secondaryCtaLabel: loc === 'de' ? 'Projekt anfragen' : 'Request Project',
secondaryCtaHref: `/${loc}/kontakt`
}
},
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'homeSubCompanyTiles',
badge: loc === 'de' ? 'Die Gruppe' : 'The Group',
title: loc === 'de' ? 'Geballte Kompetenz unter einem Dach' : 'Concentrated competence under one roof'
}
},
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'homeCompetenceBentoGrid',
badge: loc === 'de' ? 'Leistungsspektrum' : 'Range of Services',
title: loc === 'de' ? 'Umfassende Lösungen für komplexe Netzwerke' : 'Comprehensive solutions for complex networks',
ctaLabel: loc === 'de' ? 'Alle Kompetenzen ansehen' : 'View all competences',
ctaHref: `/${loc}/kompetenzen`
}
},
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'homeMeetTheTeam',
title: loc === 'de' ? 'Das sind Ihre Ansprechpartner' : 'Meet our team',
subtitle: loc === 'de' ? 'Unser Team aus Experten freut sich auf Ihr Projekt.' : 'Our team of experts is looking forward to your project.',
description: loc === 'de' ? 'Hier finden Sie alle Zertifikate und Ansprechpartner an einem Ort.' : 'Here you can find all certificates and contacts in one place.',
ctaLabel: loc === 'de' ? 'Zum Kontaktformular' : 'To Contact Form',
networkLabel: loc === 'de' ? 'Das E-TIB Netzwerk' : 'The E-TIB Network'
}
}
]
}
}
};
if (existingHome.totalDocs === 0) {
payload.logger.info(`Creating Home page [${loc}]...`);
await payload.create({ collection: 'pages', locale: loc as any, data: homeData });
} else {
await payload.update({ collection: 'pages', id: existingHome.docs[0].id, locale: loc as any, data: homeData });
}
}
payload.logger.info('✅ Home page created successfully!');
process.exit(0);
} catch (err: any) {
payload.logger.error('❌ Missing Home page script failed!');
if (err.data) {
payload.logger.error(JSON.stringify(err.data, null, 2));
} else {
payload.logger.error(err.stack || err.message || err);
}
process.exit(1);
}
};
fixHome();

View File

@@ -1,158 +0,0 @@
import { getPayload } from 'payload';
import configPromise from '../payload.config';
const fixPages = async () => {
const payload = await getPayload({ config: configPromise });
payload.logger.info('Running missing pages alignment script...');
const teamMembers = [
{ name: 'Danny Joseph', position: 'Geschäftsführung', email: 'd.joseph@e-tib.com', phone: '015207230518', branch: 'e-tib' },
{ name: 'Dirk Schmidt', position: 'Projektleitung', email: 'd.schmidt@e-tib.com', phone: '015165069040', branch: 'e-tib' },
{ name: 'Oliver Fiedler', position: 'Bauleitung', email: 'o.fiedler@e-tib.com', phone: '015152264087', branch: 'e-tib' },
{ name: 'Martin Polan', position: 'Polier', email: 'm.polan@e-tib.com', phone: '01713868167', branch: 'e-tib' },
{ name: 'Kerstin Joseph', position: 'Sekretariat / Buchhaltung', email: 'k.joseph@e-tib.com', phone: '03561 6851692', branch: 'verw' },
{ name: 'Kathrin Lemanski', position: 'Sachbearbeitung', email: 'k.lemanski@e-tib.com', phone: '03561 6857733', branch: 'verw' },
{ name: 'Sven Kirbs', position: 'Bauleitung', email: 's.kirbs@e-tib.com', phone: '', branch: 'e-tib' },
{ name: 'Maik Weichert', position: 'Bauleitung', email: 'm.weichert@e-tib.com', phone: '', branch: 'e-tib' },
];
try {
// Seed Team Members
for (const member of teamMembers) {
const existing = await payload.find({
collection: 'team',
where: { name: { equals: member.name } },
limit: 1,
});
if (existing.totalDocs === 0) {
payload.logger.info(`Creating team member: ${member.name}`);
await payload.create({
collection: 'team',
data: {
name: member.name,
position: member.position,
email: member.email,
phone: member.phone,
branch: member.branch as any,
},
});
}
}
// Create Kontakt and Karriere pages
for (const loc of ['de', 'en']) {
// Kontakt
const existingKontakt = await payload.find({
collection: 'pages',
where: { slug: { equals: 'kontakt' } },
locale: loc as any,
limit: 1,
});
const kontaktData = {
title: loc === 'de' ? 'Kontakt' : 'Contact',
slug: 'kontakt',
_status: 'published',
content: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'heroSection',
title: loc === 'de' ? 'Kontaktieren Sie uns' : 'Contact Us',
description: loc === 'de' ? 'Ihre Ansprechpartner der E-TIB Gruppe' : 'Your contacts at the E-TIB Group'
}
},
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'homeMeetTheTeam',
title: loc === 'de' ? 'Das sind Ihre Ansprechpartner' : 'Meet our team',
subtitle: loc === 'de' ? 'Unser Team aus Experten freut sich auf Ihr Projekt.' : 'Our team of experts is looking forward to your project.',
description: loc === 'de' ? 'Hier finden Sie alle Zertifikate und Ansprechpartner an einem Ort.' : 'Here you can find all certificates and contacts in one place.',
ctaLabel: loc === 'de' ? 'Zum Kontaktformular' : 'To Contact Form',
networkLabel: loc === 'de' ? 'Das E-TIB Netzwerk' : 'The E-TIB Network'
}
}
]
}
}
};
if (existingKontakt.totalDocs === 0) {
payload.logger.info(`Creating Kontakt page [${loc}]...`);
await payload.create({ collection: 'pages', locale: loc as any, data: kontaktData });
} else {
await payload.update({ collection: 'pages', id: existingKontakt.docs[0].id, locale: loc as any, data: kontaktData });
}
// Karriere
const existingKarriere = await payload.find({
collection: 'pages',
where: { slug: { equals: 'karriere' } },
locale: loc as any,
limit: 1,
});
const karriereData = {
title: loc === 'de' ? 'Karriere & Messen' : 'Career & Exhibitions',
slug: 'karriere',
_status: 'published',
content: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'heroSection',
title: loc === 'de' ? 'Karriere bei E-TIB' : 'Career at E-TIB',
description: loc === 'de' ? 'Wir wachsen und werfen gemeinsam mit Dir die Energie-Wende an.' : 'We are growing - and fueling the energy transition together.'
}
},
{
type: 'paragraph',
children: [{ type: 'text', text: loc === 'de' ? 'Trefft uns auf diesen kommenden Messen:\n- Intersolar München\n- Windenergietage Linstow\n- Kabelwerkstatt Wiesbaden' : 'Meet us at these upcoming exhibitions:\n- Intersolar Munich\n- Wind Energy Days Linstow\n- Cable Workshop Wiesbaden', version: 1 }]
}
]
}
}
};
if (existingKarriere.totalDocs === 0) {
payload.logger.info(`Creating Karriere page [${loc}]...`);
await payload.create({ collection: 'pages', locale: loc as any, data: karriereData });
} else {
await payload.update({ collection: 'pages', id: existingKarriere.docs[0].id, locale: loc as any, data: karriereData });
}
}
payload.logger.info('✅ Missing pages created successfully!');
process.exit(0);
} catch (err: any) {
payload.logger.error('❌ Missing pages script failed!');
if (err.data) {
payload.logger.error(JSON.stringify(err.data, null, 2));
} else {
payload.logger.error(err.stack || err.message || err);
}
process.exit(1);
}
};
fixPages();

View File

@@ -1,81 +0,0 @@
import { getPayload } from 'payload';
import configPromise from '../payload.config';
const createReferenzen = async () => {
const payload = await getPayload({ config: configPromise });
payload.logger.info('Creating Referenzen...');
try {
for (const loc of ['de', 'en']) {
const existing = await payload.find({
collection: 'pages',
where: { slug: { equals: 'referenzen' } },
locale: loc as any,
limit: 1,
});
const referenzenData = {
title: loc === 'de' ? 'Referenzen' : 'References',
slug: 'referenzen',
_status: 'published',
content: {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
children: [
{
type: 'block',
format: '',
version: 2,
fields: {
blockType: 'heroSection',
title: loc === 'de' ? 'Unsere Referenzen' : 'Our References',
description: loc === 'de' ? 'Projekte die uns stolz machen.' : 'Projects that make us proud.'
}
}
]
}
}
};
if (existing.totalDocs === 0) {
await payload.create({ collection: 'pages', locale: loc as any, data: referenzenData });
} else {
await payload.update({ collection: 'pages', id: existing.docs[0].id, locale: loc as any, data: referenzenData });
}
// update setings again
const navde = [
{ label: 'Unternehmen', url: '/' },
{ label: 'Kompetenzen', url: '/kompetenzen' },
{ label: 'Referenzen', url: '/referenzen' },
{ label: 'Karriere', url: '/karriere' }
];
const naven = [
{ label: 'Company', url: '/' },
{ label: 'Competences', url: '/kompetenzen' },
{ label: 'References', url: '/referenzen' },
{ label: 'Career', url: '/karriere' }
];
await payload.updateGlobal({
slug: 'settings',
locale: loc as any,
data: {
mainNav: loc === 'de' ? navde : naven
}
});
}
payload.logger.info('✅ Referenzen created!');
process.exit(0);
} catch (err: any) {
payload.logger.error('❌ Failed!');
process.exit(1);
}
};
createReferenzen();

View File

@@ -1,49 +0,0 @@
import { getPayload } from 'payload';
import configPromise from '../payload.config';
const updateSettings = async () => {
const payload = await getPayload({ config: configPromise });
payload.logger.info('Updating Global Settings...');
try {
for (const loc of ['de', 'en']) {
const navde = [
{ label: 'Unternehmen', url: '/' },
{ label: 'Kompetenzen', url: '/kompetenzen' },
{ label: 'Referenzen', url: '/startseite' },
{ label: 'Karriere', url: '/karriere' }
];
const naven = [
{ label: 'Company', url: '/' },
{ label: 'Competences', url: '/kompetenzen' },
{ label: 'References', url: '/startseite' },
{ label: 'Career', url: '/karriere' }
];
await payload.updateGlobal({
slug: 'settings',
locale: loc as any,
data: {
contactEmail: 'd.joseph@e-tib.com',
contactPhone: '+49 15207230518',
address: 'Gewerbestraße 22\n03172 Guben',
mainNav: loc === 'de' ? navde : naven
}
});
}
payload.logger.info('✅ Settings updated successfully!');
process.exit(0);
} catch (err: any) {
payload.logger.error('❌ Settings update failed!');
if (err.data) {
payload.logger.error(JSON.stringify(err.data, null, 2));
} else {
payload.logger.error(err.stack || err.message || err);
}
process.exit(1);
}
};
updateSettings();

View File

@@ -1,21 +0,0 @@
import { getPayload } from 'payload';
import configPromise from '../payload.config';
import { seedDatabase } from '../src/payload/seed';
const seed = async () => {
const payload = await getPayload({ config: configPromise });
payload.logger.info('Starting manual database seed...');
try {
await seedDatabase(payload);
payload.logger.info('✅ Database seed completed successfully.');
process.exit(0);
} catch (err) {
payload.logger.error('❌ Database seed failed:');
payload.logger.error(err);
process.exit(1);
}
};
seed();

View File

@@ -1,22 +0,0 @@
import Redis from 'ioredis';
const isDockerContainer =
process.env.IS_DOCKER === 'true' || process.env.HOSTNAME?.includes('klz-app');
const redisUrl =
process.env.REDIS_URL ||
(isDockerContainer ? 'redis://klz-redis:6379' : 'redis://localhost:6379');
// Only create a single instance in Node.js
const globalForRedis = global as unknown as { redis: Redis };
export const redis =
globalForRedis.redis ||
new Redis(redisUrl, {
maxRetriesPerRequest: 3,
});
if (process.env.NODE_ENV !== 'production') {
globalForRedis.redis = redis;
}
export default redis;

File diff suppressed because it is too large Load Diff

View File

@@ -1,356 +0,0 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
export async function up({ db }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TYPE "public"."enum_posts_locale" AS ENUM('en', 'de');
CREATE TYPE "public"."enum_posts_status" AS ENUM('draft', 'published');
CREATE TYPE "public"."enum__posts_v_version_locale" AS ENUM('en', 'de');
CREATE TYPE "public"."enum__posts_v_version_status" AS ENUM('draft', 'published');
CREATE TYPE "public"."enum_form_submissions_type" AS ENUM('contact', 'product_quote');
CREATE TYPE "public"."enum_products_locale" AS ENUM('en', 'de');
CREATE TYPE "public"."enum_products_status" AS ENUM('draft', 'published');
CREATE TYPE "public"."enum__products_v_version_locale" AS ENUM('en', 'de');
CREATE TYPE "public"."enum__products_v_version_status" AS ENUM('draft', 'published');
CREATE TABLE "users_sessions" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"created_at" timestamp(3) with time zone,
"expires_at" timestamp(3) with time zone NOT NULL
);
CREATE TABLE "users" (
"id" serial PRIMARY KEY NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"email" varchar NOT NULL,
"reset_password_token" varchar,
"reset_password_expiration" timestamp(3) with time zone,
"salt" varchar,
"hash" varchar,
"login_attempts" numeric DEFAULT 0,
"lock_until" timestamp(3) with time zone
);
CREATE TABLE "media" (
"id" serial PRIMARY KEY NOT NULL,
"alt" varchar NOT NULL,
"caption" varchar,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"url" varchar,
"thumbnail_u_r_l" varchar,
"filename" varchar,
"mime_type" varchar,
"filesize" numeric,
"width" numeric,
"height" numeric,
"focal_x" numeric,
"focal_y" numeric,
"sizes_thumbnail_url" varchar,
"sizes_thumbnail_width" numeric,
"sizes_thumbnail_height" numeric,
"sizes_thumbnail_mime_type" varchar,
"sizes_thumbnail_filesize" numeric,
"sizes_thumbnail_filename" varchar,
"sizes_card_url" varchar,
"sizes_card_width" numeric,
"sizes_card_height" numeric,
"sizes_card_mime_type" varchar,
"sizes_card_filesize" numeric,
"sizes_card_filename" varchar,
"sizes_hero_url" varchar,
"sizes_hero_width" numeric,
"sizes_hero_height" numeric,
"sizes_hero_mime_type" varchar,
"sizes_hero_filesize" numeric,
"sizes_hero_filename" varchar,
"sizes_hero_mobile_url" varchar,
"sizes_hero_mobile_width" numeric,
"sizes_hero_mobile_height" numeric,
"sizes_hero_mobile_mime_type" varchar,
"sizes_hero_mobile_filesize" numeric,
"sizes_hero_mobile_filename" varchar
);
CREATE TABLE "posts" (
"id" serial PRIMARY KEY NOT NULL,
"title" varchar,
"slug" varchar,
"excerpt" varchar,
"date" timestamp(3) with time zone,
"featured_image_id" integer,
"locale" "enum_posts_locale" DEFAULT 'en',
"category" varchar,
"content" jsonb,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"_status" "enum_posts_status" DEFAULT 'draft'
);
CREATE TABLE "_posts_v" (
"id" serial PRIMARY KEY NOT NULL,
"parent_id" integer,
"version_title" varchar,
"version_slug" varchar,
"version_excerpt" varchar,
"version_date" timestamp(3) with time zone,
"version_featured_image_id" integer,
"version_locale" "enum__posts_v_version_locale" DEFAULT 'en',
"version_category" varchar,
"version_content" jsonb,
"version_updated_at" timestamp(3) with time zone,
"version_created_at" timestamp(3) with time zone,
"version__status" "enum__posts_v_version_status" DEFAULT 'draft',
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"latest" boolean
);
CREATE TABLE "form_submissions" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar NOT NULL,
"email" varchar NOT NULL,
"type" "enum_form_submissions_type" NOT NULL,
"product_name" varchar,
"message" varchar NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE TABLE "products_categories" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"category" varchar
);
CREATE TABLE "products" (
"id" serial PRIMARY KEY NOT NULL,
"title" varchar,
"sku" varchar,
"slug" varchar,
"description" varchar,
"locale" "enum_products_locale" DEFAULT 'de',
"application" jsonb,
"content" jsonb,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"_status" "enum_products_status" DEFAULT 'draft'
);
CREATE TABLE "products_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"media_id" integer
);
CREATE TABLE "_products_v_version_categories" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"category" varchar,
"_uuid" varchar
);
CREATE TABLE "_products_v" (
"id" serial PRIMARY KEY NOT NULL,
"parent_id" integer,
"version_title" varchar,
"version_sku" varchar,
"version_slug" varchar,
"version_description" varchar,
"version_locale" "enum__products_v_version_locale" DEFAULT 'de',
"version_application" jsonb,
"version_content" jsonb,
"version_updated_at" timestamp(3) with time zone,
"version_created_at" timestamp(3) with time zone,
"version__status" "enum__products_v_version_status" DEFAULT 'draft',
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"latest" boolean
);
CREATE TABLE "_products_v_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"media_id" integer
);
CREATE TABLE "payload_kv" (
"id" serial PRIMARY KEY NOT NULL,
"key" varchar NOT NULL,
"data" jsonb NOT NULL
);
CREATE TABLE "payload_locked_documents" (
"id" serial PRIMARY KEY NOT NULL,
"global_slug" varchar,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE TABLE "payload_locked_documents_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"users_id" integer,
"media_id" integer,
"posts_id" integer,
"form_submissions_id" integer,
"products_id" integer
);
CREATE TABLE "payload_preferences" (
"id" serial PRIMARY KEY NOT NULL,
"key" varchar,
"value" jsonb,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
CREATE TABLE "payload_preferences_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"users_id" integer
);
CREATE TABLE "payload_migrations" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar,
"batch" numeric,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
ALTER TABLE "users_sessions" ADD CONSTRAINT "users_sessions_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "posts" ADD CONSTRAINT "posts_featured_image_id_media_id_fk" FOREIGN KEY ("featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "_posts_v" ADD CONSTRAINT "_posts_v_parent_id_posts_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."posts"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "_posts_v" ADD CONSTRAINT "_posts_v_version_featured_image_id_media_id_fk" FOREIGN KEY ("version_featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "products_categories" ADD CONSTRAINT "products_categories_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "products_rels" ADD CONSTRAINT "products_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "products_rels" ADD CONSTRAINT "products_rels_media_fk" FOREIGN KEY ("media_id") REFERENCES "public"."media"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_products_v_version_categories" ADD CONSTRAINT "_products_v_version_categories_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_products_v"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_products_v" ADD CONSTRAINT "_products_v_parent_id_products_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."products"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "_products_v_rels" ADD CONSTRAINT "_products_v_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."_products_v"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_products_v_rels" ADD CONSTRAINT "_products_v_rels_media_fk" FOREIGN KEY ("media_id") REFERENCES "public"."media"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_locked_documents"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_media_fk" FOREIGN KEY ("media_id") REFERENCES "public"."media"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_posts_fk" FOREIGN KEY ("posts_id") REFERENCES "public"."posts"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_form_submissions_fk" FOREIGN KEY ("form_submissions_id") REFERENCES "public"."form_submissions"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_products_fk" FOREIGN KEY ("products_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."payload_preferences"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_users_fk" FOREIGN KEY ("users_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
CREATE INDEX "users_sessions_order_idx" ON "users_sessions" USING btree ("_order");
CREATE INDEX "users_sessions_parent_id_idx" ON "users_sessions" USING btree ("_parent_id");
CREATE INDEX "users_updated_at_idx" ON "users" USING btree ("updated_at");
CREATE INDEX "users_created_at_idx" ON "users" USING btree ("created_at");
CREATE UNIQUE INDEX "users_email_idx" ON "users" USING btree ("email");
CREATE INDEX "media_updated_at_idx" ON "media" USING btree ("updated_at");
CREATE INDEX "media_created_at_idx" ON "media" USING btree ("created_at");
CREATE UNIQUE INDEX "media_filename_idx" ON "media" USING btree ("filename");
CREATE INDEX "media_sizes_thumbnail_sizes_thumbnail_filename_idx" ON "media" USING btree ("sizes_thumbnail_filename");
CREATE INDEX "media_sizes_card_sizes_card_filename_idx" ON "media" USING btree ("sizes_card_filename");
CREATE INDEX "media_sizes_hero_sizes_hero_filename_idx" ON "media" USING btree ("sizes_hero_filename");
CREATE INDEX "media_sizes_hero_mobile_sizes_hero_mobile_filename_idx" ON "media" USING btree ("sizes_hero_mobile_filename");
CREATE UNIQUE INDEX "posts_slug_idx" ON "posts" USING btree ("slug");
CREATE INDEX "posts_featured_image_idx" ON "posts" USING btree ("featured_image_id");
CREATE INDEX "posts_updated_at_idx" ON "posts" USING btree ("updated_at");
CREATE INDEX "posts_created_at_idx" ON "posts" USING btree ("created_at");
CREATE INDEX "posts__status_idx" ON "posts" USING btree ("_status");
CREATE INDEX "_posts_v_parent_idx" ON "_posts_v" USING btree ("parent_id");
CREATE INDEX "_posts_v_version_version_slug_idx" ON "_posts_v" USING btree ("version_slug");
CREATE INDEX "_posts_v_version_version_featured_image_idx" ON "_posts_v" USING btree ("version_featured_image_id");
CREATE INDEX "_posts_v_version_version_updated_at_idx" ON "_posts_v" USING btree ("version_updated_at");
CREATE INDEX "_posts_v_version_version_created_at_idx" ON "_posts_v" USING btree ("version_created_at");
CREATE INDEX "_posts_v_version_version__status_idx" ON "_posts_v" USING btree ("version__status");
CREATE INDEX "_posts_v_created_at_idx" ON "_posts_v" USING btree ("created_at");
CREATE INDEX "_posts_v_updated_at_idx" ON "_posts_v" USING btree ("updated_at");
CREATE INDEX "_posts_v_latest_idx" ON "_posts_v" USING btree ("latest");
CREATE INDEX "form_submissions_updated_at_idx" ON "form_submissions" USING btree ("updated_at");
CREATE INDEX "form_submissions_created_at_idx" ON "form_submissions" USING btree ("created_at");
CREATE INDEX "products_categories_order_idx" ON "products_categories" USING btree ("_order");
CREATE INDEX "products_categories_parent_id_idx" ON "products_categories" USING btree ("_parent_id");
CREATE UNIQUE INDEX "products_sku_idx" ON "products" USING btree ("sku");
CREATE INDEX "products_updated_at_idx" ON "products" USING btree ("updated_at");
CREATE INDEX "products_created_at_idx" ON "products" USING btree ("created_at");
CREATE INDEX "products__status_idx" ON "products" USING btree ("_status");
CREATE INDEX "products_rels_order_idx" ON "products_rels" USING btree ("order");
CREATE INDEX "products_rels_parent_idx" ON "products_rels" USING btree ("parent_id");
CREATE INDEX "products_rels_path_idx" ON "products_rels" USING btree ("path");
CREATE INDEX "products_rels_media_id_idx" ON "products_rels" USING btree ("media_id");
CREATE INDEX "_products_v_version_categories_order_idx" ON "_products_v_version_categories" USING btree ("_order");
CREATE INDEX "_products_v_version_categories_parent_id_idx" ON "_products_v_version_categories" USING btree ("_parent_id");
CREATE INDEX "_products_v_parent_idx" ON "_products_v" USING btree ("parent_id");
CREATE INDEX "_products_v_version_version_sku_idx" ON "_products_v" USING btree ("version_sku");
CREATE INDEX "_products_v_version_version_updated_at_idx" ON "_products_v" USING btree ("version_updated_at");
CREATE INDEX "_products_v_version_version_created_at_idx" ON "_products_v" USING btree ("version_created_at");
CREATE INDEX "_products_v_version_version__status_idx" ON "_products_v" USING btree ("version__status");
CREATE INDEX "_products_v_created_at_idx" ON "_products_v" USING btree ("created_at");
CREATE INDEX "_products_v_updated_at_idx" ON "_products_v" USING btree ("updated_at");
CREATE INDEX "_products_v_latest_idx" ON "_products_v" USING btree ("latest");
CREATE INDEX "_products_v_rels_order_idx" ON "_products_v_rels" USING btree ("order");
CREATE INDEX "_products_v_rels_parent_idx" ON "_products_v_rels" USING btree ("parent_id");
CREATE INDEX "_products_v_rels_path_idx" ON "_products_v_rels" USING btree ("path");
CREATE INDEX "_products_v_rels_media_id_idx" ON "_products_v_rels" USING btree ("media_id");
CREATE UNIQUE INDEX "payload_kv_key_idx" ON "payload_kv" USING btree ("key");
CREATE INDEX "payload_locked_documents_global_slug_idx" ON "payload_locked_documents" USING btree ("global_slug");
CREATE INDEX "payload_locked_documents_updated_at_idx" ON "payload_locked_documents" USING btree ("updated_at");
CREATE INDEX "payload_locked_documents_created_at_idx" ON "payload_locked_documents" USING btree ("created_at");
CREATE INDEX "payload_locked_documents_rels_order_idx" ON "payload_locked_documents_rels" USING btree ("order");
CREATE INDEX "payload_locked_documents_rels_parent_idx" ON "payload_locked_documents_rels" USING btree ("parent_id");
CREATE INDEX "payload_locked_documents_rels_path_idx" ON "payload_locked_documents_rels" USING btree ("path");
CREATE INDEX "payload_locked_documents_rels_users_id_idx" ON "payload_locked_documents_rels" USING btree ("users_id");
CREATE INDEX "payload_locked_documents_rels_media_id_idx" ON "payload_locked_documents_rels" USING btree ("media_id");
CREATE INDEX "payload_locked_documents_rels_posts_id_idx" ON "payload_locked_documents_rels" USING btree ("posts_id");
CREATE INDEX "payload_locked_documents_rels_form_submissions_id_idx" ON "payload_locked_documents_rels" USING btree ("form_submissions_id");
CREATE INDEX "payload_locked_documents_rels_products_id_idx" ON "payload_locked_documents_rels" USING btree ("products_id");
CREATE INDEX "payload_preferences_key_idx" ON "payload_preferences" USING btree ("key");
CREATE INDEX "payload_preferences_updated_at_idx" ON "payload_preferences" USING btree ("updated_at");
CREATE INDEX "payload_preferences_created_at_idx" ON "payload_preferences" USING btree ("created_at");
CREATE INDEX "payload_preferences_rels_order_idx" ON "payload_preferences_rels" USING btree ("order");
CREATE INDEX "payload_preferences_rels_parent_idx" ON "payload_preferences_rels" USING btree ("parent_id");
CREATE INDEX "payload_preferences_rels_path_idx" ON "payload_preferences_rels" USING btree ("path");
CREATE INDEX "payload_preferences_rels_users_id_idx" ON "payload_preferences_rels" USING btree ("users_id");
CREATE INDEX "payload_migrations_updated_at_idx" ON "payload_migrations" USING btree ("updated_at");
CREATE INDEX "payload_migrations_created_at_idx" ON "payload_migrations" USING btree ("created_at");`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
DROP TABLE "users_sessions" CASCADE;
DROP TABLE "users" CASCADE;
DROP TABLE "media" CASCADE;
DROP TABLE "posts" CASCADE;
DROP TABLE "_posts_v" CASCADE;
DROP TABLE "form_submissions" CASCADE;
DROP TABLE "products_categories" CASCADE;
DROP TABLE "products" CASCADE;
DROP TABLE "products_rels" CASCADE;
DROP TABLE "_products_v_version_categories" CASCADE;
DROP TABLE "_products_v" CASCADE;
DROP TABLE "_products_v_rels" CASCADE;
DROP TABLE "payload_kv" CASCADE;
DROP TABLE "payload_locked_documents" CASCADE;
DROP TABLE "payload_locked_documents_rels" CASCADE;
DROP TABLE "payload_preferences" CASCADE;
DROP TABLE "payload_preferences_rels" CASCADE;
DROP TABLE "payload_migrations" CASCADE;
DROP TYPE "public"."enum_posts_locale";
DROP TYPE "public"."enum_posts_status";
DROP TYPE "public"."enum__posts_v_version_locale";
DROP TYPE "public"."enum__posts_v_version_status";
DROP TYPE "public"."enum_form_submissions_type";
DROP TYPE "public"."enum_products_locale";
DROP TYPE "public"."enum_products_status";
DROP TYPE "public"."enum__products_v_version_locale";
DROP TYPE "public"."enum__products_v_version_status";`);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
export async function up({ db }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
DROP INDEX "products_sku_idx";
DROP INDEX "_products_v_version_version_sku_idx";`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
CREATE UNIQUE INDEX "products_sku_idx" ON "products" USING btree ("sku");
CREATE INDEX "_products_v_version_version_sku_idx" ON "_products_v" USING btree ("version_sku");`);
}

View File

@@ -1,5 +0,0 @@
{
"id": "20260225_003500_add_pages_collection",
"name": "20260225_003500_add_pages_collection",
"batch": 3
}

View File

@@ -1,48 +0,0 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
export async function up({ db }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_pages_locale" AS ENUM('en', 'de');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
CREATE TABLE IF NOT EXISTS "pages" (
"id" serial PRIMARY KEY NOT NULL,
"title" varchar NOT NULL,
"slug" varchar NOT NULL,
"locale" "enum_pages_locale" NOT NULL,
"excerpt" varchar,
"featured_image_id" integer,
"content" jsonb NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
ALTER TABLE "pages" ADD CONSTRAINT "pages_featured_image_id_media_id_fk"
FOREIGN KEY ("featured_image_id") REFERENCES "public"."media"("id")
ON DELETE set null ON UPDATE no action;
CREATE INDEX IF NOT EXISTS "pages_featured_image_idx" ON "pages" USING btree ("featured_image_id");
CREATE INDEX IF NOT EXISTS "pages_updated_at_idx" ON "pages" USING btree ("updated_at");
CREATE INDEX IF NOT EXISTS "pages_created_at_idx" ON "pages" USING btree ("created_at");
-- Add pages_id to payload_locked_documents_rels if not already present
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN IF NOT EXISTS "pages_id" integer;
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_pages_fk"
FOREIGN KEY ("pages_id") REFERENCES "public"."pages"("id")
ON DELETE cascade ON UPDATE no action;
CREATE INDEX IF NOT EXISTS "payload_locked_documents_rels_pages_id_idx"
ON "payload_locked_documents_rels" USING btree ("pages_id");
`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT IF EXISTS "payload_locked_documents_rels_pages_fk";
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN IF EXISTS "pages_id";
DROP TABLE IF EXISTS "pages" CASCADE;
DROP TYPE IF EXISTS "public"."enum_pages_locale";
`);
}

View File

@@ -1,358 +0,0 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
/**
* Migration: native_localization
*
* Transforms the existing schema (manual `locale` select column on each row)
* into Payload's native localization join-table structure.
*
* Each statement is a separate db.execute call to avoid Drizzle multi-statement issues.
*/
export async function up({ db }: MigrateUpArgs): Promise<void> {
// ── 1. Global locale enum ────────────────────────────────────────────────────
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__locales" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__posts_v_published_locale" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__products_v_published_locale" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__pages_v_published_locale" AS ENUM('de', 'en');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_pages_layout" AS ENUM('default', 'fullBleed');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__pages_v_version_layout" AS ENUM('default', 'fullBleed');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__pages_v_version_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__posts_v_version_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum__products_v_version_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_pages_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_posts_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_products_status" AS ENUM('draft', 'published');
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 2. Alter pages table ─────────────────────────────────────────────────────
await db.execute(
sql`ALTER TABLE "pages" ADD COLUMN IF NOT EXISTS "layout" "enum_pages_layout" DEFAULT 'default'`,
);
await db.execute(
sql`ALTER TABLE "pages" ADD COLUMN IF NOT EXISTS "_status" "enum_pages_status" DEFAULT 'draft'`,
);
// ── 3. Create pages_locales join table ───────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "pages_locales" (
"title" varchar,
"slug" varchar,
"excerpt" varchar,
"content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL,
"_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "pages_locales" ADD CONSTRAINT "pages_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "pages_locales" ADD CONSTRAINT "pages_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "pages"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 4. Backfill pages_locales from old pages rows ────────────────────────────
await db.execute(sql`
INSERT INTO "pages_locales" ("title", "slug", "excerpt", "content", "_locale", "_parent_id")
SELECT
p.title, p.slug, p.excerpt, p.content,
CASE WHEN p.locale::text = 'de' THEN 'de'::"enum__locales" ELSE 'en'::"enum__locales" END,
p.id
FROM "pages" p
WHERE p.locale IS NOT NULL
ON CONFLICT ("_locale", "_parent_id") DO UPDATE
SET "title" = EXCLUDED."title",
"slug" = EXCLUDED."slug",
"excerpt" = EXCLUDED."excerpt",
"content" = EXCLUDED."content"
`);
// ── 5. Drop old columns from pages ───────────────────────────────────────────
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "title"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "slug"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "excerpt"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "content"`);
await db.execute(sql`ALTER TABLE "pages" DROP COLUMN IF EXISTS "locale"`);
// ── 6. Create posts_locales join table ───────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "posts_locales" (
"title" varchar,
"slug" varchar,
"excerpt" varchar,
"category" varchar,
"content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL,
"_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "posts_locales" ADD CONSTRAINT "posts_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "posts_locales" ADD CONSTRAINT "posts_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "posts"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 7. Backfill posts_locales ────────────────────────────────────────────────
await db.execute(sql`
INSERT INTO "posts_locales" ("title", "slug", "excerpt", "category", "content", "_locale", "_parent_id")
SELECT
p.title, p.slug, p.excerpt, p.category, p.content,
CASE WHEN p.locale::text = 'de' THEN 'de'::"enum__locales" ELSE 'en'::"enum__locales" END,
p.id
FROM "posts" p
WHERE p.locale IS NOT NULL
ON CONFLICT ("_locale", "_parent_id") DO UPDATE
SET "title" = EXCLUDED."title",
"slug" = EXCLUDED."slug",
"excerpt" = EXCLUDED."excerpt",
"category" = EXCLUDED."category",
"content" = EXCLUDED."content"
`);
// ── 8. Drop old columns from posts ───────────────────────────────────────────
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "title"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "slug"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "excerpt"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "category"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "content"`);
await db.execute(sql`ALTER TABLE "posts" DROP COLUMN IF EXISTS "locale"`);
// ── 9. Create products_locales join table ────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "products_locales" (
"title" varchar,
"description" varchar,
"application" jsonb,
"content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL,
"_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "products_locales" ADD CONSTRAINT "products_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "products_locales" ADD CONSTRAINT "products_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "products"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 10. Backfill products_locales ────────────────────────────────────────────
// Products were separate DE/EN rows — each becomes a locale entry on its own id
await db.execute(sql`
INSERT INTO "products_locales" ("title", "description", "application", "content", "_locale", "_parent_id")
SELECT
p.title, p.description, p.application, p.content,
CASE WHEN p.locale::text = 'de' THEN 'de'::"enum__locales" ELSE 'en'::"enum__locales" END,
p.id
FROM "products" p
WHERE p.locale IS NOT NULL
ON CONFLICT ("_locale", "_parent_id") DO NOTHING
`);
// ── 11. Drop old columns from products ───────────────────────────────────────
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "title"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "description"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "application"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "content"`);
await db.execute(sql`ALTER TABLE "products" DROP COLUMN IF EXISTS "locale"`);
// ── 12. Version tables (_posts_v, _products_v, _pages_v) locale columns ──────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_posts_v" (
"id" serial PRIMARY KEY NOT NULL,
"parent_id" integer,
"version_title" varchar,
"version_slug" varchar,
"version_excerpt" varchar,
"version_content" jsonb,
"version_updated_at" timestamp(3) with time zone,
"version_created_at" timestamp(3) with time zone,
"version__status" "enum__posts_v_version_status" DEFAULT 'draft',
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"latest" boolean
)
`);
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_products_v" (
"id" serial PRIMARY KEY NOT NULL,
"parent_id" integer,
"version_title" varchar,
"version_description" varchar,
"version_content" jsonb,
"version_updated_at" timestamp(3) with time zone,
"version_created_at" timestamp(3) with time zone,
"version__status" "enum__products_v_version_status" DEFAULT 'draft',
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"latest" boolean
)
`);
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_pages_v" (
"id" serial PRIMARY KEY NOT NULL,
"parent_id" integer,
"version_title" varchar,
"version_slug" varchar,
"version_excerpt" varchar,
"version_content" jsonb,
"version_updated_at" timestamp(3) with time zone,
"version_created_at" timestamp(3) with time zone,
"version__status" "enum__pages_v_version_status" DEFAULT 'draft',
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"latest" boolean
)
`);
await db.execute(
sql`ALTER TABLE "_posts_v" ADD COLUMN IF NOT EXISTS "published_locale" "enum__posts_v_published_locale"`,
);
await db.execute(
sql`ALTER TABLE "_products_v" ADD COLUMN IF NOT EXISTS "published_locale" "enum__products_v_published_locale"`,
);
await db.execute(
sql`ALTER TABLE "_pages_v" ADD COLUMN IF NOT EXISTS "published_locale" "enum__pages_v_published_locale"`,
);
// ── 13. Create _posts_v_locales ──────────────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_posts_v_locales" (
"version_title" varchar, "version_slug" varchar, "version_excerpt" varchar,
"version_category" varchar, "version_content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL, "_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_posts_v_locales" ADD CONSTRAINT "_posts_v_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_posts_v_locales" ADD CONSTRAINT "_posts_v_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "_posts_v"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 14. Create _products_v_locales ───────────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_products_v_locales" (
"version_title" varchar, "version_description" varchar,
"version_application" jsonb, "version_content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL, "_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_products_v_locales" ADD CONSTRAINT "_products_v_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_products_v_locales" ADD CONSTRAINT "_products_v_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "_products_v"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 15. Create _pages_v_locales ──────────────────────────────────────────────
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "_pages_v_locales" (
"version_title" varchar, "version_slug" varchar,
"version_excerpt" varchar, "version_content" jsonb,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "enum__locales" NOT NULL, "_parent_id" integer NOT NULL
)
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_pages_v_locales" ADD CONSTRAINT "_pages_v_locales_locale_parent_id_unique" UNIQUE("_locale", "_parent_id");
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_pages_v_locales" ADD CONSTRAINT "_pages_v_locales_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "_pages_v"("id") ON DELETE cascade;
EXCEPTION WHEN duplicate_object THEN null; END $$
`);
// ── 16. Drop the now-redundant old locale enum ───────────────────────────────
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_pages_locale"`);
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_posts_locale"`);
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_products_locale"`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`DROP TABLE IF EXISTS "pages_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "_pages_v_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "posts_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "_posts_v_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "products_locales" CASCADE`);
await db.execute(sql`DROP TABLE IF EXISTS "_products_v_locales" CASCADE`);
}

View File

@@ -1,52 +0,0 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
export async function up({ db }: MigrateUpArgs): Promise<void> {
// Add featured_image_id to products and _products_v
await db.execute(sql`
ALTER TABLE "products" ADD COLUMN IF NOT EXISTS "featured_image_id" integer;
`);
await db.execute(sql`
ALTER TABLE "_products_v" ADD COLUMN IF NOT EXISTS "version_featured_image_id" integer;
`);
// Add foreign key constraints
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "products" ADD CONSTRAINT "products_featured_image_id_media_id_fk" FOREIGN KEY ("featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null; END $$;
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "_products_v" ADD CONSTRAINT "_products_v_version_featured_image_id_media_id_fk" FOREIGN KEY ("version_featured_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null; END $$;
`);
// Add indexes
await db.execute(sql`
CREATE INDEX IF NOT EXISTS "products_featured_image_idx" ON "products" USING btree ("featured_image_id");
`);
await db.execute(sql`
CREATE INDEX IF NOT EXISTS "_products_v_version_version_featured_image_idx" ON "_products_v" USING btree ("version_featured_image_id");
`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "products" DROP CONSTRAINT IF EXISTS "products_featured_image_id_media_id_fk";
`);
await db.execute(sql`
ALTER TABLE "_products_v" DROP CONSTRAINT IF EXISTS "_products_v_version_featured_image_id_media_id_fk";
`);
await db.execute(sql`
DROP INDEX IF EXISTS "products_featured_image_idx";
`);
await db.execute(sql`
DROP INDEX IF EXISTS "_products_v_version_version_featured_image_idx";
`);
await db.execute(sql`
ALTER TABLE "products" DROP COLUMN IF EXISTS "featured_image_id";
`);
await db.execute(sql`
ALTER TABLE "_products_v" DROP COLUMN IF EXISTS "version_featured_image_id";
`);
}

View File

@@ -1,31 +0,0 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres';
export async function up({ db }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_url" varchar;
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_width" numeric;
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_height" numeric;
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_mime_type" varchar;
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_filesize" numeric;
ALTER TABLE "media" ADD COLUMN IF NOT EXISTS "sizes_tablet_filename" varchar;
`);
await db.execute(sql`
CREATE INDEX IF NOT EXISTS "media_sizes_tablet_sizes_tablet_filename_idx" ON "media" USING btree ("sizes_tablet_filename");
`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
DROP INDEX IF EXISTS "media_sizes_tablet_sizes_tablet_filename_idx";
`);
await db.execute(sql`
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_filename";
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_filesize";
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_mime_type";
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_height";
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_width";
ALTER TABLE "media" DROP COLUMN IF EXISTS "sizes_tablet_url";
`);
}

View File

@@ -1,39 +0,0 @@
import * as migration_20260223_195005_products_collection from './20260223_195005_products_collection';
import * as migration_20260223_195151_remove_sku_unique from './20260223_195151_remove_sku_unique';
import * as migration_20260225_003500_add_pages_collection from './20260225_003500_add_pages_collection';
import * as migration_20260225_175000_native_localization from './20260225_175000_native_localization';
import * as migration_20260305_215000_products_featured_image from './20260305_215000_products_featured_image';
import * as migration_20260305_222000_media_sizes_tablet from './20260305_222000_media_sizes_tablet';
export const migrations = [
{
up: migration_20260223_195005_products_collection.up,
down: migration_20260223_195005_products_collection.down,
name: '20260223_195005_products_collection',
},
{
up: migration_20260223_195151_remove_sku_unique.up,
down: migration_20260223_195151_remove_sku_unique.down,
name: '20260223_195151_remove_sku_unique',
},
{
up: migration_20260225_003500_add_pages_collection.up,
down: migration_20260225_003500_add_pages_collection.down,
name: '20260225_003500_add_pages_collection',
},
{
up: migration_20260225_175000_native_localization.up,
down: migration_20260225_175000_native_localization.down,
name: '20260225_175000_native_localization',
},
{
up: migration_20260305_215000_products_featured_image.up,
down: migration_20260305_215000_products_featured_image.down,
name: '20260305_215000_products_featured_image',
},
{
up: migration_20260305_222000_media_sizes_tablet.up,
down: migration_20260305_222000_media_sizes_tablet.down,
name: '20260305_222000_media_sizes_tablet',
},
];

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +0,0 @@
import type { Access } from 'payload';
export const anyone: Access = () => true;

View File

@@ -1,5 +0,0 @@
import type { Access } from 'payload';
export const authenticated: Access = ({ req: { user } }) => {
return Boolean(user);
};

View File

@@ -1,27 +0,0 @@
import { EtibBlock } from './types';
import AnimatedImageComponent from '@/components/blog/AnimatedImage';
export const AnimatedImage: EtibBlock = {
slug: 'animatedImage',
render: AnimatedImageComponent,
fields: [
{
name: 'src',
type: 'text',
required: true,
},
{
name: 'alt',
type: 'text',
required: true,
},
{
name: 'width',
type: 'number',
},
{
name: 'height',
type: 'number',
},
],
};

View File

@@ -1,22 +0,0 @@
import { EtibBlock } from './types';
import { Callout as CalloutComponent } from '@/components/ui/Callout';
import { lexicalEditor } from '@payloadcms/richtext-lexical';
export const Callout: EtibBlock = {
slug: 'callout',
render: CalloutComponent,
fields: [
{
name: 'type',
type: 'select',
options: ['info', 'warning', 'important', 'tip', 'caution'],
defaultValue: 'info',
},
{
name: 'content',
type: 'richText',
editor: lexicalEditor({}),
required: true,
},
],
};

View File

@@ -1,48 +0,0 @@
import { Block } from 'payload';
export const CategoryGrid: Block = {
slug: 'categoryGrid',
interfaceName: 'CategoryGridBlock',
fields: [
{
name: 'categories',
type: 'array',
required: true,
minRows: 1,
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'description',
type: 'textarea',
required: false,
},
{
name: 'image',
type: 'upload',
relationTo: 'media',
required: false,
},
{
name: 'icon',
type: 'upload',
relationTo: 'media',
required: false,
},
{
name: 'href',
type: 'text',
required: true,
},
{
name: 'ctaLabel',
type: 'text',
required: false,
},
],
},
],
};

View File

@@ -1,36 +0,0 @@
import { EtibBlock } from './types';
import { lexicalEditor } from '@payloadcms/richtext-lexical';
import ChatBubbleComponent from '@/components/blog/ChatBubble';
export const ChatBubble: EtibBlock = {
slug: 'chatBubble',
render: ChatBubbleComponent,
fields: [
{
name: 'author',
type: 'text',
defaultValue: 'KLZ Team',
},
{
name: 'avatar',
type: 'text',
},
{
name: 'role',
type: 'text',
defaultValue: 'Assistant',
},
{
name: 'align',
type: 'select',
options: ['left', 'right'],
defaultValue: 'left',
},
{
name: 'content',
type: 'richText',
editor: lexicalEditor({}),
required: true,
},
],
};

View File

@@ -1,54 +0,0 @@
import { Block } from 'payload';
export const TeamLegacySection: Block = {
slug: 'teamLegacySection',
interfaceName: 'TeamLegacySectionBlock',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'subtitle',
type: 'text',
required: true,
},
{
name: 'paragraph1',
type: 'textarea',
required: true,
},
{
name: 'paragraph2',
type: 'textarea',
required: true,
},
{
name: 'expertiseTitle',
type: 'text',
required: true,
},
{
name: 'expertiseDesc',
type: 'text',
required: true,
},
{
name: 'networkTitle',
type: 'text',
required: true,
},
{
name: 'networkDesc',
type: 'text',
required: true,
},
{
name: 'backgroundImage',
type: 'upload',
relationTo: 'media',
required: false,
},
],
};

View File

@@ -1,12 +0,0 @@
import { EtibBlock } from './types';
import { CompanyTimeline } from '@/components/blocks/CompanyTimeline';
export const CompanyTimelineBlock: EtibBlock = {
slug: 'companyTimeline',
interfaceName: 'CompanyTimelineBlock',
render: CompanyTimeline,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
],
};

View File

@@ -1,49 +0,0 @@
import { EtibBlock } from './types';
import ComparisonGridComponent from '@/components/blog/ComparisonGrid';
export const ComparisonGrid: EtibBlock = {
slug: 'comparisonGrid',
render: ComparisonGridComponent,
fields: [
{
name: 'title',
label: 'Main Heading',
type: 'text',
required: true,
},
{
name: 'leftLabel',
type: 'text',
required: true,
},
{
name: 'rightLabel',
type: 'text',
required: true,
},
{
name: 'items',
type: 'array',
required: true,
minRows: 1,
fields: [
{
name: 'label',
label: 'Row Label',
type: 'text',
required: true,
},
{
name: 'leftValue',
type: 'text',
required: true,
},
{
name: 'rightValue',
type: 'text',
required: true,
},
],
},
],
};

View File

@@ -1,28 +0,0 @@
import { EtibBlock } from './types';
import { ContactSection as ContactSectionComponent } from '@/components/blocks/ContactSection';
export const ContactSection: EtibBlock = {
slug: 'contactSection',
interfaceName: 'ContactSectionBlock',
render: ContactSectionComponent,
fields: [
{
name: 'showForm',
type: 'checkbox',
defaultValue: true,
label: 'Show Contact Form',
},
{
name: 'showMap',
type: 'checkbox',
defaultValue: true,
label: 'Show Map',
},
{
name: 'showHours',
type: 'checkbox',
defaultValue: true,
label: 'Show Opening Hours',
},
],
};

View File

@@ -1,50 +0,0 @@
import { EtibBlock } from './types';
import { HeroSection as HeroSectionComponent } from '@/components/blocks/HeroSection';
export const HeroSection: EtibBlock = {
slug: 'heroSection',
interfaceName: 'HeroSectionBlock',
render: HeroSectionComponent,
fields: [
{
name: 'badge',
type: 'text',
required: false,
},
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'subtitle',
type: 'textarea',
required: false,
},
{
name: 'backgroundImage',
type: 'upload',
relationTo: 'media',
required: false,
},
{
name: 'ctaLabel',
type: 'text',
required: false,
},
{
name: 'ctaHref',
type: 'text',
required: false,
},
{
name: 'alignment',
type: 'select',
defaultValue: 'left',
options: [
{ label: 'Left', value: 'left' },
{ label: 'Center', value: 'center' },
],
},
],
};

View File

@@ -1,20 +0,0 @@
import { Block } from 'payload';
import { lexicalEditor } from '@payloadcms/richtext-lexical';
export const HighlightBox: Block = {
slug: 'highlightBox',
fields: [
{
name: 'type',
type: 'select',
options: ['info', 'warning', 'success', 'error', 'neutral'],
defaultValue: 'neutral',
},
{
name: 'content',
type: 'richText',
editor: lexicalEditor({}),
required: true,
},
],
};

View File

@@ -1,240 +0,0 @@
import { EtibBlock } from './types';
import { HeroVideo } from '@/components/blocks/HeroVideo';
import { SubCompanyTiles } from '@/components/blocks/SubCompanyTiles';
import { CompetenceBentoGrid } from '@/components/blocks/CompetenceBentoGrid';
import { ReferencesSlider } from '@/components/blocks/ReferencesSlider';
import Hero from '@/components/home/Hero';
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';
export const HomeHero: EtibBlock = {
slug: 'homeHero',
interfaceName: 'HomeHeroBlock',
render: HeroVideo,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'videoUrl', type: 'text', admin: { description: 'URL to the background mp4 video' } },
{ name: 'posterImage', type: 'upload', relationTo: 'media' },
{ name: 'ctaLabel', type: 'text', localized: true },
{ name: 'ctaHref', type: 'text' },
{ name: 'secondaryCtaLabel', type: 'text', localized: true },
{ name: 'secondaryCtaHref', type: 'text' },
],
};
export const HomeSubCompanyTiles: EtibBlock = {
slug: 'homeSubCompanyTiles',
interfaceName: 'HomeSubCompanyTilesBlock',
render: SubCompanyTiles,
fields: [
{ name: 'badge', type: 'text', localized: true },
{ name: 'title', type: 'text', localized: true },
{
name: 'companies',
type: 'array',
localized: true,
fields: [
{ name: 'title', type: 'text' },
{ name: 'description', type: 'textarea' },
{ name: 'icon', type: 'text', admin: { description: 'SVG path for the icon' } },
{ name: 'backgroundImage', type: 'upload', relationTo: 'media' },
],
},
],
};
export const HomeCompetenceBentoGrid: EtibBlock = {
slug: 'homeCompetenceBentoGrid',
interfaceName: 'HomeCompetenceBentoGridBlock',
render: CompetenceBentoGrid,
fields: [
{ name: 'badge', type: 'text', localized: true },
{ name: 'title', type: 'text', localized: true },
{ name: 'ctaLabel', type: 'text', localized: true },
{ name: 'ctaHref', type: 'text' },
{
name: 'items',
type: 'array',
localized: true,
fields: [
{ name: 'title', type: 'text' },
{ name: 'description', type: 'textarea' },
{ name: 'tag', type: 'text' },
{ name: 'image', type: 'upload', relationTo: 'media' },
{
name: 'size',
type: 'select',
options: [
{ label: 'Large (2x2)', value: 'large' },
{ label: 'Medium (1x2)', value: 'medium' },
{ label: 'Small (1x1)', value: 'small' },
{ label: 'Accent (Text only)', value: 'accent' },
],
defaultValue: 'small',
},
],
},
],
};
export const HomeReferencesSlider: EtibBlock = {
slug: 'homeReferencesSlider',
interfaceName: 'HomeReferencesSliderBlock',
render: ReferencesSlider,
fields: [
{ name: 'badge', type: 'text', localized: true },
{ name: 'title', type: 'text', localized: true },
{ name: 'description', type: 'textarea', localized: true },
{ name: 'ctaLabel', type: 'text', localized: true },
{ name: 'ctaHref', type: 'text' },
],
};
export const HomeProductCategories: EtibBlock = {
slug: 'homeProductCategories',
interfaceName: 'HomeProductCategoriesBlock',
render: ProductCategories,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
],
};
export const HomeWhatWeDo: EtibBlock = {
slug: 'homeWhatWeDo',
interfaceName: 'HomeWhatWeDoBlock',
render: WhatWeDo,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'expertiseLabel', type: 'text', localized: true },
{ name: 'quote', type: 'textarea', localized: true },
{
name: 'items',
type: 'array',
localized: true,
fields: [
{ name: 'title', type: 'text' },
{ name: 'description', type: 'textarea' },
],
},
],
};
export const HomeRecentPosts: EtibBlock = {
slug: 'homeRecentPosts',
interfaceName: 'HomeRecentPostsBlock',
render: RecentPosts,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
],
};
export const HomeExperience: EtibBlock = {
slug: 'homeExperience',
interfaceName: 'HomeExperienceBlock',
render: Experience,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'paragraph1', type: 'textarea', localized: true },
{ name: 'paragraph2', type: 'textarea', localized: true },
{ name: 'badge1', type: 'text', localized: true },
{ name: 'badge1Text', type: 'text', localized: true },
{ name: 'badge2', type: 'text', localized: true },
{ name: 'badge2Text', type: 'text', localized: true },
],
};
export const HomeWhyChooseUs: EtibBlock = {
slug: 'homeWhyChooseUs',
interfaceName: 'HomeWhyChooseUsBlock',
render: WhyChooseUs,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'tagline', type: 'text', localized: true },
{
name: 'features',
type: 'array',
localized: true,
fields: [{ name: 'feature', type: 'text' }],
},
{
name: 'items',
type: 'array',
localized: true,
fields: [
{ name: 'title', type: 'text' },
{ name: 'description', type: 'textarea' },
],
},
],
};
export const HomeMeetTheTeam: EtibBlock = {
slug: 'homeMeetTheTeam',
interfaceName: 'HomeMeetTheTeamBlock',
render: MeetTheTeam,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'description', type: 'textarea', localized: true },
{ name: 'ctaLabel', type: 'text', localized: true },
{ name: 'networkLabel', type: 'text', localized: true },
],
};
export const HomeGallery: EtibBlock = {
slug: 'homeGallery',
interfaceName: 'HomeGalleryBlock',
render: GallerySection,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
],
};
export const HomeVideo: EtibBlock = {
slug: 'homeVideo',
interfaceName: 'HomeVideoBlock',
render: VideoSection,
fields: [{ name: 'title', type: 'text', localized: true }],
};
export const HomeCTA: EtibBlock = {
slug: 'homeCTA',
interfaceName: 'HomeCTABlock',
render: CTA,
fields: [
{ name: 'title', type: 'text', localized: true },
{ name: 'subtitle', type: 'text', localized: true },
{ name: 'description', type: 'textarea', localized: true },
{ name: 'buttonLabel', type: 'text', localized: true },
],
};
export const homeBlocksArray = [
HomeHero,
HomeSubCompanyTiles,
HomeCompetenceBentoGrid,
HomeReferencesSlider,
HomeProductCategories,
HomeWhatWeDo,
HomeRecentPosts,
HomeExperience,
HomeWhyChooseUs,
HomeMeetTheTeam,
HomeGallery,
HomeVideo,
HomeCTA,
];

Some files were not shown because too many files have changed in this diff Show More