From 6a5e4011047ee28d144e933470f218341aad0f97 Mon Sep 17 00:00:00 2001 From: alexandrev-tibco Date: Tue, 5 May 2026 15:34:30 +0200 Subject: [PATCH] Add n8n feedback form --- README.md | 8 +++ app/api/feedback/route.ts | 31 +++++++++ components/FeedbackModal.tsx | 123 +++++++++++++++++++++++++++++++++++ components/HomeClient.tsx | 19 +++++- deploy/k8s/deployment.yaml | 6 ++ lib/i18n.ts | 70 ++++++++++++++++++++ 6 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 app/api/feedback/route.ts create mode 100644 components/FeedbackModal.tsx diff --git a/README.md b/README.md index 6748448..1056543 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ Optional analytics env vars: - `NEXT_PUBLIC_GA_MEASUREMENT_ID`: GA4 measurement ID, e.g. `G-XXXXXXXXXX` - `NEXT_PUBLIC_CLARITY_PROJECT_ID`: Microsoft Clarity project ID +Optional feedback env vars: + +- `N8N_FEEDBACK_WEBHOOK_URL`: n8n webhook URL used to receive feedback submissions + ## Production build ```bash @@ -65,6 +69,10 @@ To enable analytics in Kubernetes, create a secret named `hidden11-analytics` in - `NEXT_PUBLIC_GA_MEASUREMENT_ID` - `NEXT_PUBLIC_CLARITY_PROJECT_ID` +To enable feedback submission, create a secret named `hidden11-feedback` in the `hidden11` namespace with: + +- `N8N_FEEDBACK_WEBHOOK_URL` + ## ArgoCD ```bash diff --git a/app/api/feedback/route.ts b/app/api/feedback/route.ts new file mode 100644 index 0000000..1c9abf0 --- /dev/null +++ b/app/api/feedback/route.ts @@ -0,0 +1,31 @@ +import { NextResponse } from "next/server"; + +export async function POST(request: Request) { + const webhookUrl = process.env.N8N_FEEDBACK_WEBHOOK_URL; + if (!webhookUrl) { + return NextResponse.json({ error: "feedback webhook not configured" }, { status: 503 }); + } + + const body = await request.json().catch(() => null); + if (!body || typeof body !== "object") { + return NextResponse.json({ error: "invalid payload" }, { status: 400 }); + } + + const response = await fetch(webhookUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + ...body, + receivedAt: new Date().toISOString(), + userAgent: request.headers.get("user-agent") ?? "", + }), + }); + + if (!response.ok) { + return NextResponse.json({ error: "upstream error" }, { status: 502 }); + } + + return NextResponse.json({ ok: true }); +} diff --git a/components/FeedbackModal.tsx b/components/FeedbackModal.tsx new file mode 100644 index 0000000..f3c1b18 --- /dev/null +++ b/components/FeedbackModal.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { useState, type FormEvent } from "react"; +import type { Dictionary } from "@/lib/i18n"; + +type FeedbackType = "bug" | "suggestion" | "general"; + +type FeedbackModalProps = { + dateKey: string; + onClose: () => void; + t: Dictionary; +}; + +export default function FeedbackModal({ dateKey, onClose, t }: FeedbackModalProps) { + const [type, setType] = useState("general"); + const [message, setMessage] = useState(""); + const [isSending, setIsSending] = useState(false); + const [status, setStatus] = useState<"idle" | "success" | "error">("idle"); + + async function submit(event: FormEvent) { + event.preventDefault(); + if (!message.trim() || isSending) return; + + setIsSending(true); + setStatus("idle"); + + try { + const response = await fetch("/api/feedback", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + dateKey, + message: message.trim(), + type, + page: window.location.pathname, + locale: window.navigator.language, + source: "hidden11", + }), + }); + + if (!response.ok) { + throw new Error("feedback request failed"); + } + + setStatus("success"); + setMessage(""); + } catch { + setStatus("error"); + } finally { + setIsSending(false); + } + } + + return ( +
+
+
+
+

+ {t.feedback} +

+

Tell us what to improve.

+
+ +
+ +
+ + +