fix(i18n): render consent banner inside NextIntl provider

Move ConsentBanner rendering into the locale layout so next-intl context is always available (prevents missing provider errors).

fix(activity-feed): use dark styling for disabled state

Avoid white disabled cards so the feed never appears as a white/transparent block after reload.

test(e2e): assert nav text changes on locale switch

Strengthen i18n test to verify translated labels.

Co-authored-by: dennis <dennis@konkol.net>
This commit is contained in:
Cursor Agent
2026-01-14 16:09:22 +00:00
parent 9082bd256a
commit 9364b44196
5 changed files with 20 additions and 13 deletions

View File

@@ -1,6 +1,7 @@
import { NextIntlClientProvider } from "next-intl";
import { getMessages, setRequestLocale } from "next-intl/server";
import React from "react";
import ConsentBanner from "../components/ConsentBanner";
export default async function LocaleLayout({
children,
@@ -17,6 +18,7 @@ export default async function LocaleLayout({
return (
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
<ConsentBanner />
</NextIntlClientProvider>
);
}

View File

@@ -1490,20 +1490,20 @@ export default function ActivityFeed() {
<motion.div
initial={false}
animate={{ scale: 1, opacity: 1 }}
className="pointer-events-auto bg-white/90 backdrop-blur-2xl border border-white/60 rounded-2xl shadow-xl overflow-hidden w-full"
className="pointer-events-auto bg-black/95 backdrop-blur-2xl border border-white/10 rounded-2xl shadow-2xl overflow-hidden w-full"
>
<div className="px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-3">
<Activity size={18} className="text-stone-900" />
<Activity size={18} className="text-white" />
<div className="text-left">
<h3 className="text-sm font-bold text-stone-900">Live Activity</h3>
<p className="text-[10px] text-stone-500">Tracking disabled</p>
<h3 className="text-sm font-bold text-white">Live Activity</h3>
<p className="text-[10px] text-white/50">Tracking disabled</p>
</div>
</div>
<button
type="button"
onClick={toggleTracking}
className="text-xs font-semibold px-3 py-1.5 rounded-full bg-stone-900 text-white hover:bg-stone-800 transition-colors"
className="text-xs font-semibold px-3 py-1.5 rounded-full bg-white/10 text-white hover:bg-white/15 transition-colors border border-white/10"
title="Enable activity tracking"
>
Enable
@@ -1521,20 +1521,20 @@ export default function ActivityFeed() {
<motion.div
initial={false}
animate={{ scale: 1, opacity: 1 }}
className="pointer-events-auto bg-white/90 backdrop-blur-2xl border border-white/60 rounded-2xl shadow-xl overflow-hidden w-full"
className="pointer-events-auto bg-black/95 backdrop-blur-2xl border border-white/10 rounded-2xl shadow-2xl overflow-hidden w-full"
>
<div className="px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-3">
<Activity size={18} className="text-stone-900" />
<Activity size={18} className="text-white" />
<div className="text-left">
<h3 className="text-sm font-bold text-stone-900">Live Activity</h3>
<p className="text-[10px] text-stone-500">Tracking disabled</p>
<h3 className="text-sm font-bold text-white">Live Activity</h3>
<p className="text-[10px] text-white/50">Tracking disabled</p>
</div>
</div>
<button
type="button"
onClick={toggleTracking}
className="text-xs font-semibold px-3 py-1.5 rounded-full bg-stone-900 text-white hover:bg-stone-800 transition-colors"
className="text-xs font-semibold px-3 py-1.5 rounded-full bg-white/10 text-white hover:bg-white/15 transition-colors border border-white/10"
title="Enable activity tracking"
>
Enable

View File

@@ -7,7 +7,6 @@ import { ToastProvider } from "@/components/Toast";
import ErrorBoundary from "@/components/ErrorBoundary";
import { AnalyticsProvider } from "@/components/AnalyticsProvider";
import { ConsentProvider, useConsent } from "./ConsentProvider";
import ConsentBanner from "./ConsentBanner";
// Dynamic import with SSR disabled to avoid framer-motion issues
const BackgroundBlobs = dynamic(() => import("@/components/BackgroundBlobs").catch(() => ({ default: () => null })), {
@@ -105,7 +104,6 @@ function GatedProviders({
<ToastProvider>
{mounted && <BackgroundBlobs />}
<div className="relative z-10">{children}</div>
{mounted && !isAdminRoute && <ConsentBanner />}
{mounted && !is404Page && !isAdminRoute && chatEnabled && <ChatWidget />}
</ToastProvider>
</ErrorBoundary>

View File

@@ -7,8 +7,14 @@ test.describe("i18n routing", () => {
// Buttons are "EN"/"DE" in the header
const deButton = page.getByRole("button", { name: "DE" });
if (await deButton.count()) {
// Verify an EN label is present before switching (nav.home)
await expect(page.getByRole("link", { name: "Home" })).toBeVisible();
await deButton.click();
await expect(page).toHaveURL(/\/de(\/|$)/);
// Verify the nav label updates after switching
await expect(page.getByRole("link", { name: "Start" })).toBeVisible();
} else {
test.skip();
}

View File

@@ -44,7 +44,8 @@ export default defineConfig({
],
webServer: {
command: 'npm run dev',
// Use plain Next.js dev server for E2E (no Docker dependency)
command: 'npm run dev:next',
url: 'http://localhost:3000',
reuseExistingServer: true, // Always reuse if server is running
timeout: 120 * 1000,