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:
@@ -1,6 +1,7 @@
|
|||||||
import { NextIntlClientProvider } from "next-intl";
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
import { getMessages, setRequestLocale } from "next-intl/server";
|
import { getMessages, setRequestLocale } from "next-intl/server";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import ConsentBanner from "../components/ConsentBanner";
|
||||||
|
|
||||||
export default async function LocaleLayout({
|
export default async function LocaleLayout({
|
||||||
children,
|
children,
|
||||||
@@ -17,6 +18,7 @@ export default async function LocaleLayout({
|
|||||||
return (
|
return (
|
||||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
{children}
|
{children}
|
||||||
|
<ConsentBanner />
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1490,20 +1490,20 @@ export default function ActivityFeed() {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
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="px-4 py-3 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Activity size={18} className="text-stone-900" />
|
<Activity size={18} className="text-white" />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<h3 className="text-sm font-bold text-stone-900">Live Activity</h3>
|
<h3 className="text-sm font-bold text-white">Live Activity</h3>
|
||||||
<p className="text-[10px] text-stone-500">Tracking disabled</p>
|
<p className="text-[10px] text-white/50">Tracking disabled</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleTracking}
|
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"
|
title="Enable activity tracking"
|
||||||
>
|
>
|
||||||
Enable
|
Enable
|
||||||
@@ -1521,20 +1521,20 @@ export default function ActivityFeed() {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
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="px-4 py-3 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Activity size={18} className="text-stone-900" />
|
<Activity size={18} className="text-white" />
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<h3 className="text-sm font-bold text-stone-900">Live Activity</h3>
|
<h3 className="text-sm font-bold text-white">Live Activity</h3>
|
||||||
<p className="text-[10px] text-stone-500">Tracking disabled</p>
|
<p className="text-[10px] text-white/50">Tracking disabled</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleTracking}
|
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"
|
title="Enable activity tracking"
|
||||||
>
|
>
|
||||||
Enable
|
Enable
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { ToastProvider } from "@/components/Toast";
|
|||||||
import ErrorBoundary from "@/components/ErrorBoundary";
|
import ErrorBoundary from "@/components/ErrorBoundary";
|
||||||
import { AnalyticsProvider } from "@/components/AnalyticsProvider";
|
import { AnalyticsProvider } from "@/components/AnalyticsProvider";
|
||||||
import { ConsentProvider, useConsent } from "./ConsentProvider";
|
import { ConsentProvider, useConsent } from "./ConsentProvider";
|
||||||
import ConsentBanner from "./ConsentBanner";
|
|
||||||
|
|
||||||
// Dynamic import with SSR disabled to avoid framer-motion issues
|
// Dynamic import with SSR disabled to avoid framer-motion issues
|
||||||
const BackgroundBlobs = dynamic(() => import("@/components/BackgroundBlobs").catch(() => ({ default: () => null })), {
|
const BackgroundBlobs = dynamic(() => import("@/components/BackgroundBlobs").catch(() => ({ default: () => null })), {
|
||||||
@@ -105,7 +104,6 @@ function GatedProviders({
|
|||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
{mounted && <BackgroundBlobs />}
|
{mounted && <BackgroundBlobs />}
|
||||||
<div className="relative z-10">{children}</div>
|
<div className="relative z-10">{children}</div>
|
||||||
{mounted && !isAdminRoute && <ConsentBanner />}
|
|
||||||
{mounted && !is404Page && !isAdminRoute && chatEnabled && <ChatWidget />}
|
{mounted && !is404Page && !isAdminRoute && chatEnabled && <ChatWidget />}
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|||||||
@@ -7,8 +7,14 @@ test.describe("i18n routing", () => {
|
|||||||
// Buttons are "EN"/"DE" in the header
|
// Buttons are "EN"/"DE" in the header
|
||||||
const deButton = page.getByRole("button", { name: "DE" });
|
const deButton = page.getByRole("button", { name: "DE" });
|
||||||
if (await deButton.count()) {
|
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 deButton.click();
|
||||||
await expect(page).toHaveURL(/\/de(\/|$)/);
|
await expect(page).toHaveURL(/\/de(\/|$)/);
|
||||||
|
|
||||||
|
// Verify the nav label updates after switching
|
||||||
|
await expect(page.getByRole("link", { name: "Start" })).toBeVisible();
|
||||||
} else {
|
} else {
|
||||||
test.skip();
|
test.skip();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
|
|
||||||
webServer: {
|
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',
|
url: 'http://localhost:3000',
|
||||||
reuseExistingServer: true, // Always reuse if server is running
|
reuseExistingServer: true, // Always reuse if server is running
|
||||||
timeout: 120 * 1000,
|
timeout: 120 * 1000,
|
||||||
|
|||||||
Reference in New Issue
Block a user