fix(consent): avoid banner flashing on reload
Initialize consent state from cookie synchronously so the banner only shows when no choice was made. fix(api): fail-soft when DB schema missing Return null/empty content for CMS endpoints when migrations are not applied instead of crashing with Prisma P2021/P2022. fix(n8n): parse status response defensively Handle empty/invalid JSON bodies from n8n to prevent activity feed from getting stuck. Co-authored-by: dennis <dennis@konkol.net>
This commit is contained in:
@@ -10,9 +10,16 @@ export async function GET(request: NextRequest) {
|
||||
return NextResponse.json({ error: "key is required" }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const translation = await getContentByKey({ key, locale });
|
||||
if (!translation) return NextResponse.json({ content: null });
|
||||
|
||||
return NextResponse.json({ content: translation });
|
||||
} catch (error) {
|
||||
// If DB isn't migrated/available, fail soft so the UI can fall back to next-intl strings.
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
console.warn("Content API failed; returning null content:", error);
|
||||
}
|
||||
return NextResponse.json({ content: null });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ export async function GET(request: NextRequest) {
|
||||
const res = await fetch(statusUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
// n8n sometimes responds with empty body; we'll parse defensively below.
|
||||
Accept: "application/json",
|
||||
...(process.env.N8N_SECRET_TOKEN && {
|
||||
Authorization: `Bearer ${process.env.N8N_SECRET_TOKEN}`,
|
||||
}),
|
||||
@@ -71,7 +72,21 @@ export async function GET(request: NextRequest) {
|
||||
throw new Error(`n8n error: ${res.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const raw = await res.text().catch(() => "");
|
||||
if (!raw || !raw.trim()) {
|
||||
throw new Error("Empty response body received from n8n");
|
||||
}
|
||||
|
||||
let data: unknown;
|
||||
try {
|
||||
data = JSON.parse(raw);
|
||||
} catch (parseError) {
|
||||
// Sometimes upstream sends HTML or a partial response; include a snippet for debugging.
|
||||
const snippet = raw.slice(0, 240);
|
||||
throw new Error(
|
||||
`Invalid JSON from n8n (${res.status}): ${snippet}${raw.length > 240 ? "…" : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
// n8n gibt oft ein Array zurück: [{...}]. Wir wollen nur das Objekt.
|
||||
const statusData = Array.isArray(data) ? data[0] : data;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import React, { createContext, useCallback, useContext, useMemo, useState } from "react";
|
||||
|
||||
export type ConsentState = {
|
||||
analytics: boolean;
|
||||
@@ -46,11 +46,8 @@ const ConsentContext = createContext<{
|
||||
});
|
||||
|
||||
export function ConsentProvider({ children }: { children: React.ReactNode }) {
|
||||
const [consent, setConsentState] = useState<ConsentState | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setConsentState(readConsentFromCookie());
|
||||
}, []);
|
||||
// Read cookie synchronously so we don't flash the banner on every reload.
|
||||
const [consent, setConsentState] = useState<ConsentState | null>(() => readConsentFromCookie());
|
||||
|
||||
const setConsent = useCallback((next: ConsentState) => {
|
||||
setConsentState(next);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||
|
||||
export async function getSiteSettings() {
|
||||
return prisma.siteSettings.findUnique({ where: { id: 1 } });
|
||||
@@ -7,6 +8,7 @@ export async function getSiteSettings() {
|
||||
|
||||
export async function getContentByKey(opts: { key: string; locale: string }) {
|
||||
const { key, locale } = opts;
|
||||
try {
|
||||
const page = await prisma.contentPage.findUnique({
|
||||
where: { key },
|
||||
include: {
|
||||
@@ -30,6 +32,14 @@ export async function getContentByKey(opts: { key: string; locale: string }) {
|
||||
});
|
||||
|
||||
return fallback;
|
||||
} catch (error) {
|
||||
// If migrations haven't been applied yet, don't crash the app.
|
||||
// Let callers fall back to static translations.
|
||||
if (error instanceof PrismaClientKnownRequestError && (error.code === "P2021" || error.code === "P2022")) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function upsertContentByKey(opts: {
|
||||
|
||||
Reference in New Issue
Block a user