From 2cfe9115d24a1a074df2d5526012b32e0bf2dd62 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Mon, 13 Apr 2026 21:11:23 +0200 Subject: [PATCH] feat: automate availability and fix KLZ showcase 404s --- apps/web/app/(site)/globals.css | 22 +++++++++++ apps/web/app/(site)/page.tsx | 3 +- apps/web/src/components/Availability.tsx | 26 +++++++++++++ apps/web/src/middleware.ts | 47 ++++++++++++++---------- apps/web/src/styles/global.css | 22 +++++++++++ 5 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 apps/web/src/components/Availability.tsx diff --git a/apps/web/app/(site)/globals.css b/apps/web/app/(site)/globals.css index 4a3ff14..670000e 100644 --- a/apps/web/app/(site)/globals.css +++ b/apps/web/app/(site)/globals.css @@ -78,6 +78,28 @@ box-shadow: none !important; } + /* Interactive elements pointer */ + a, + button, + [role="button"], + input[type="button"], + input[type="submit"], + input[type="reset"], + select, + summary, + label[for] { + cursor: pointer; + } + + /* Ensure disabled elements don't show pointer */ + button:disabled, + [role="button"]:disabled, + input:disabled, + select:disabled, + button[disabled] { + cursor: not-allowed; + } + /* Remove default tap highlight on mobile */ * { -webkit-tap-highlight-color: transparent !important; diff --git a/apps/web/app/(site)/page.tsx b/apps/web/app/(site)/page.tsx index 8ef5edc..563096c 100644 --- a/apps/web/app/(site)/page.tsx +++ b/apps/web/app/(site)/page.tsx @@ -26,6 +26,7 @@ import { HeroSection } from "@/src/components/HeroSection"; import { GlitchText } from "@/src/components/GlitchText"; import { Marker } from "@/src/components/Marker"; import { PenCircle } from "@/src/components/PenCircle"; +import { Availability } from "@/src/components/Availability"; export default function LandingPage() { return ( @@ -313,7 +314,7 @@ export default function LandingPage() { Aktuell nehme ich Projekte für{" "} - Q2 2026{" "} + {" "} an. diff --git a/apps/web/src/components/Availability.tsx b/apps/web/src/components/Availability.tsx new file mode 100644 index 0000000..fbf91d0 --- /dev/null +++ b/apps/web/src/components/Availability.tsx @@ -0,0 +1,26 @@ +"use client"; + +import React, { useEffect, useState } from "react"; + +/** + * Automatisierte Anzeige der Projekt-Verfügbarkeit nach Quartalen. + * Berechnet das aktuelle Quartal basierend auf dem heutigen Datum. + */ +export const Availability: React.FC = () => { + const [text, setText] = useState(""); + + useEffect(() => { + const now = new Date(); + const month = now.getMonth(); // 0-11 + const year = now.getFullYear(); + const quarter = Math.floor(month / 3) + 1; + + // Aktuelles Quartal formatiert (z.B. Q2 2026) + setText(`Q${quarter} ${year}`); + }, []); + + // Während Hydrierung nichts rendern, um Mismatches zu vermeiden (bei Client-only Logic) + if (!text) return Q- 202-; + + return <>{text}; +}; diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index 7140e3a..0367ba0 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -11,19 +11,32 @@ export function middleware(request: NextRequest) { // 2. Legacy Showcase Asset Mapping - // Matches: /wp-content/*, /wp-includes/*, /assets/*, and their relative subpaths - const showcaseAssetPattern = /^\/(?:wp-content|wp-includes|assets|(?:case-studies|work|blog)\/(?:wp-content|wp-includes|assets))\/(.*)/; - const match = pathname.match(showcaseAssetPattern); + // Handles: + // - Global assets (/wp-content/*, /wp-includes/*, /assets/*) + // - Showcase-specific resources (/case-studies/klz-cables/index.html, etc.) + + // Map Pattern 1: Global assets (directly under root) + if (pathname.match(/^\/(?:wp-content|wp-includes|assets)\//)) { + return NextResponse.rewrite(new URL(`/showcase/klz-cables.com${pathname}`, request.url)); + } - if (match) { - const assetPath = match[0]; + // Map Pattern 2: Showcase-prefixed assets + const showcaseMatch = pathname.match(/^\/(?:case-studies|work|blog)\/([^\/]+)\/(.*)/); + if (showcaseMatch) { + const [, slug, remainingPath] = showcaseMatch; - // Normalize path by stripping the case-studies/work/blog prefix if present - const normalizedPath = assetPath.replace(/^\/(?:case-studies|work|blog)/, ''); - - // Map to the literal directory in public/ - // Destination: /showcase/klz-cables.com/... - return NextResponse.rewrite(new URL(`/showcase/klz-cables.com${normalizedPath}`, request.url)); + // Safety: Only rewrite if it looks like a static asset (has extension) + // or belongs to known legacy folders. This prevents catching Next.js routes. + if (remainingPath.match(/\.(?:html|php|js|css|png|jpg|jpeg|svg|gif|woff2?|ttf|pdf|json|xml)$/) || + remainingPath.includes('wp-content/') || + remainingPath.includes('wp-includes/') || + remainingPath.includes('assets/')) { + + // Normalize slug: klz-cables -> klz-cables.com (literal folder name) + const directory = slug === 'klz-cables' ? 'klz-cables.com' : slug; + + return NextResponse.rewrite(new URL(`/showcase/${directory}/${remainingPath}`, request.url)); + } } return NextResponse.next(); @@ -35,14 +48,8 @@ export const config = { '/wp-content/:path*', '/wp-includes/:path*', '/assets/:path*', - '/case-studies/wp-content/:path*', - '/case-studies/wp-includes/:path*', - '/case-studies/assets/:path*', - '/work/wp-content/:path*', - '/work/wp-includes/:path*', - '/work/assets/:path*', - '/blog/wp-content/:path*', - '/blog/wp-includes/:path*', - '/blog/assets/:path*', + '/case-studies/:path*', + '/work/:path*', + '/blog/:path*', ], }; diff --git a/apps/web/src/styles/global.css b/apps/web/src/styles/global.css index 3f8085a..a7de385 100644 --- a/apps/web/src/styles/global.css +++ b/apps/web/src/styles/global.css @@ -79,6 +79,28 @@ outline: 2px solid #3b82f6; outline-offset: 2px; } + + /* Interactive elements pointer */ + a, + button, + [role="button"], + input[type="button"], + input[type="submit"], + input[type="reset"], + select, + summary, + label[for] { + cursor: pointer; + } + + /* Ensure disabled elements don't show pointer */ + button:disabled, + [role="button"]:disabled, + input:disabled, + select:disabled, + button[disabled] { + cursor: not-allowed; + } } /* Components - Tailwind utility classes */