From 7ec826dae38cdc2a2cb5b45bad642728bb80a303 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sun, 8 Feb 2026 21:48:55 +0100 Subject: [PATCH] feat: integrate feedback module --- .env | 12 +- .env.example | 4 +- .gitea/workflows/deploy.yml | 13 +- .gitignore | 4 +- app/[locale]/[slug]/page.tsx | 10 +- app/[locale]/blog/[slug]/page.tsx | 12 +- app/[locale]/blog/page.tsx | 10 +- app/[locale]/layout.tsx | 28 +- app/[locale]/page.tsx | 12 +- app/[locale]/team/page.tsx | 10 +- app/api/feedback/route.ts | 79 + app/api/whoami/route.ts | 43 + components/feedback/FeedbackOverlay.tsx | 539 ++++++ components/home/Hero.tsx | 2 +- directus/migrations/.keep | 0 directus/schema/feedback_schema.yaml | 590 +++++++ directus/schema/snapshot.yaml | 590 +++++++ docker-compose.override.yml | 77 +- docker-compose.yml | 7 +- docs/PLATFORM.md | 13 + i18n/request.ts | 15 +- lib/config.ts | 11 + lib/directus.ts | 47 +- lib/env.ts | 20 + messages/de.json | 2 +- messages/en.json | 2 +- next-env.d.ts | 3 +- package-lock.json | 2123 +++++++++++------------ package.json | 29 +- middleware.ts => proxy.ts | 4 +- scripts/add-status-panel.ts | 39 + scripts/cms-apply.sh | 54 + scripts/cms-snapshot.sh | 15 + scripts/container-fix.js | 33 + scripts/debug-dashboard-variants.ts | 82 + scripts/debug-label-fallback.ts | 42 + scripts/debug-list-defaults.ts | 45 + scripts/feedback.yaml | 34 + scripts/final-fix.ts | 60 + scripts/fix-collection-display.ts | 45 + scripts/fix-list-template.ts | 50 + scripts/inspect-dashboards.ts | 38 + scripts/nuke-pave.ts | 80 + scripts/rebuild-dashboards.ts | 176 ++ scripts/setup-feedback-hardened.ts | 122 ++ scripts/setup-feedback.ts | 86 + scripts/test-auth.ts | 229 +++ tsconfig.json | 40 +- 48 files changed, 4413 insertions(+), 1168 deletions(-) create mode 100644 app/api/feedback/route.ts create mode 100644 app/api/whoami/route.ts create mode 100644 components/feedback/FeedbackOverlay.tsx create mode 100644 directus/migrations/.keep create mode 100644 directus/schema/feedback_schema.yaml create mode 100644 directus/schema/snapshot.yaml rename middleware.ts => proxy.ts (92%) create mode 100644 scripts/add-status-panel.ts create mode 100755 scripts/cms-apply.sh create mode 100755 scripts/cms-snapshot.sh create mode 100644 scripts/container-fix.js create mode 100644 scripts/debug-dashboard-variants.ts create mode 100644 scripts/debug-label-fallback.ts create mode 100644 scripts/debug-list-defaults.ts create mode 100644 scripts/feedback.yaml create mode 100644 scripts/final-fix.ts create mode 100644 scripts/fix-collection-display.ts create mode 100644 scripts/fix-list-template.ts create mode 100644 scripts/inspect-dashboards.ts create mode 100644 scripts/nuke-pave.ts create mode 100644 scripts/rebuild-dashboards.ts create mode 100644 scripts/setup-feedback-hardened.ts create mode 100644 scripts/setup-feedback.ts create mode 100644 scripts/test-auth.ts diff --git a/.env b/.env index a36749f5..77083f61 100644 --- a/.env +++ b/.env @@ -2,15 +2,9 @@ NODE_ENV=production NEXT_PUBLIC_BASE_URL=https://klz-cables.com UMAMI_API_ENDPOINT=https://analytics.infra.mintel.me -NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3 SENTRY_DSN=https://c10957d0182245b1a2a806ac2d34a197@errors.infra.mintel.me/1 LOG_LEVEL=info - -# WooCommerce & WordPress -WOOCOMMERCE_URL=https://klz-cables.com -WOOCOMMERCE_CONSUMER_KEY=ck_38d97df86880e8fefbd54ab5cdf47a9c5a9e5b39 -WOOCOMMERCE_CONSUMER_SECRET=cs_d675ee2ac2ec7c22de84ae5451c07e42b1717759 -WORDPRESS_APP_PASSWORD="DlJH 49dp fC3a Itc3 Sl7Z Wz0k" +NEXT_PUBLIC_FEEDBACK_ENABLED=true # SMTP Configuration MAIL_HOST=smtp.eu.mailgun.org @@ -26,11 +20,15 @@ DIRECTUS_KEY=59fb8f4c1a51b18fe28ad947f713914e DIRECTUS_SECRET=7459038d41401dfb11254cf7f1ef2d0f DIRECTUS_ADMIN_EMAIL=marc@mintel.me DIRECTUS_ADMIN_PASSWORD=Tim300493. +DIRECTUS_API_TOKEN=59fb8f4c1a51b18fe28ad947f713914e DIRECTUS_DB_NAME=directus DIRECTUS_DB_USER=directus # Local Development PROJECT_NAME=klz-cables +GATEKEEPER_BYPASS_ENABLED=true TRAEFIK_HOST=klz.localhost DIRECTUS_HOST=cms.klz.localhost GATEKEEPER_PASSWORD=klz2026 COOKIE_DOMAIN=localhost +INFRA_DIRECTUS_URL=http://localhost:8059 +INFRA_DIRECTUS_TOKEN=59fb8f4c1a51b18fe28ad947f713914e diff --git a/.env.example b/.env.example index 64642189..21aa6f64 100644 --- a/.env.example +++ b/.env.example @@ -10,11 +10,11 @@ # ──────────────────────────────────────────────────────────────────────────── NODE_ENV=development NEXT_PUBLIC_BASE_URL=http://localhost:3000 +DIRECTUS_PORT=8055 # TARGET is used to differentiate between environments (testing, staging, production) # NEXT_PUBLIC_TARGET makes this information available to the frontend -NEXT_PUBLIC_TARGET=development -# TARGET is used server-side TARGET=development +NEXT_PUBLIC_FEEDBACK_ENABLED=false # ──────────────────────────────────────────────────────────────────────────── # Analytics (Umami) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 190e13e3..a8e0a696 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -327,7 +327,9 @@ jobs: ssh -o StrictHostKeyChecking=accept-new root@alpha.mintel.me bash << 'EOF' set -e mkdir -p /home/deploy/sites/klz-cables.com/varnish - mkdir -p /home/deploy/sites/klz-cables.com/directus/uploads /home/deploy/sites/klz-cables.com/directus/extensions + mkdir -p /home/deploy/sites/klz-cables.com/directus/uploads \ + /home/deploy/sites/klz-cables.com/directus/extensions \ + /home/deploy/sites/klz-cables.com/directus/schema if [ -d "/home/deploy/sites/klz-cables.com/varnish/default.vcl" ]; then echo "🧹 Removing directory 'varnish/default.vcl' created by Docker..." rm -rf /home/deploy/sites/klz-cables.com/varnish/default.vcl @@ -338,6 +340,7 @@ jobs: # 2. Transfer files scp -o StrictHostKeyChecking=accept-new /tmp/klz-cables.env root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/$ENV_FILE scp -o StrictHostKeyChecking=accept-new docker-compose.yml root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/docker-compose.yml + scp -r -o StrictHostKeyChecking=accept-new directus/schema root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/directus/ scp -r -o StrictHostKeyChecking=accept-new varnish root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/ ssh -o StrictHostKeyChecking=accept-new root@alpha.mintel.me IMAGE_TAG="$IMAGE_TAG" ENV_FILE="$ENV_FILE" PROJECT_NAME="$PROJECT_NAME" bash << 'EOF' @@ -361,6 +364,14 @@ jobs: docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" logs --tail=150 exit 1 fi + + echo "→ Applying Directus Schema Snapshot..." + # Note: We check if snapshot exists first to avoid failing if no snapshot is committed yet + if docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" exec -T directus ls /directus/schema/snapshot.yaml >/dev/null 2>&1; then + docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" exec -T directus npx directus schema apply /directus/schema/snapshot.yaml --yes + else + echo "ℹ️ No snapshot.yaml found, skipping schema apply." + fi echo "→ Verifying Varnish Backend Health..." docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" exec -T varnish varnishadm backend.list diff --git a/.gitignore b/.gitignore index 16cbbf11..571b25dd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ node_modules # Directus directus/uploads -!directus/extensions/ \ No newline at end of file +!directus/extensions/ +!directus/schema/ +!directus/migrations/ \ No newline at end of file diff --git a/app/[locale]/[slug]/page.tsx b/app/[locale]/[slug]/page.tsx index 3844f17f..e647a88d 100644 --- a/app/[locale]/[slug]/page.tsx +++ b/app/[locale]/[slug]/page.tsx @@ -9,10 +9,10 @@ import { getOGImageMetadata } from '@/lib/metadata'; import { SITE_URL } from '@/lib/schema'; interface PageProps { - params: { + params: Promise<{ locale: string; slug: string; - }; + }>; } export async function generateStaticParams() { @@ -29,7 +29,8 @@ export async function generateStaticParams() { return params; } -export async function generateMetadata({ params: { locale, slug } }: PageProps): Promise { +export async function generateMetadata({ params }: PageProps): Promise { + const { locale, slug } = await params; const pageData = await getPageBySlug(slug, locale); if (!pageData) return {}; @@ -59,7 +60,8 @@ export async function generateMetadata({ params: { locale, slug } }: PageProps): }; } -export default async function StandardPage({ params: { locale, slug } }: PageProps) { +export default async function StandardPage({ params }: PageProps) { + const { locale, slug } = await params; const pageData = await getPageBySlug(slug, locale); const t = await getTranslations('StandardPage'); diff --git a/app/[locale]/blog/[slug]/page.tsx b/app/[locale]/blog/[slug]/page.tsx index b75e253b..9d28a65f 100644 --- a/app/[locale]/blog/[slug]/page.tsx +++ b/app/[locale]/blog/[slug]/page.tsx @@ -14,15 +14,14 @@ import { Heading } from '@/components/ui'; import { getOGImageMetadata } from '@/lib/metadata'; interface BlogPostProps { - params: { + params: Promise<{ locale: string; slug: string; - }; + }>; } -export async function generateMetadata({ - params: { locale, slug }, -}: BlogPostProps): Promise { +export async function generateMetadata({ params }: BlogPostProps): Promise { + const { locale, slug } = await params; const post = await getPostBySlug(slug, locale); if (!post) return {}; @@ -56,7 +55,8 @@ export async function generateMetadata({ }; } -export default async function BlogPost({ params: { locale, slug } }: BlogPostProps) { +export default async function BlogPost({ params }: BlogPostProps) { + const { locale, slug } = await params; const post = await getPostBySlug(slug, locale); const { prev, next } = await getAdjacentPosts(slug, locale); diff --git a/app/[locale]/blog/page.tsx b/app/[locale]/blog/page.tsx index a0feb0ad..c6de0b69 100644 --- a/app/[locale]/blog/page.tsx +++ b/app/[locale]/blog/page.tsx @@ -7,12 +7,13 @@ import { getOGImageMetadata } from '@/lib/metadata'; import { SITE_URL } from '@/lib/schema'; interface BlogIndexProps { - params: { + params: Promise<{ locale: string; - }; + }>; } -export async function generateMetadata({ params: { locale } }: BlogIndexProps) { +export async function generateMetadata({ params }: BlogIndexProps) { + const { locale } = await params; const t = await getTranslations({ locale, namespace: 'Blog.meta' }); return { title: t('title'), @@ -39,7 +40,8 @@ export async function generateMetadata({ params: { locale } }: BlogIndexProps) { }; } -export default async function BlogIndex({ params: { locale } }: BlogIndexProps) { +export default async function BlogIndex({ params }: BlogIndexProps) { + const { locale } = await params; const t = await getTranslations('Blog'); const posts = await getAllPosts(locale); diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index b3ebf01f..48515270 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -3,6 +3,7 @@ import Header from '@/components/Header'; import JsonLd from '@/components/JsonLd'; import AnalyticsProvider from '@/components/analytics/AnalyticsProvider'; import CMSConnectivityNotice from '@/components/CMSConnectivityNotice'; +import { FeedbackOverlay } from '@/components/feedback/FeedbackOverlay'; import { Metadata, Viewport } from 'next'; import { NextIntlClientProvider } from 'next-intl'; import { getMessages } from 'next-intl/server'; @@ -32,27 +33,38 @@ export const viewport: Viewport = { export default async function LocaleLayout({ children, - params: { locale }, + params, }: { children: React.ReactNode; - params: { locale: string }; + params: Promise<{ locale: string }>; }) { - // Providing all messages to the client - // side is the easiest way to get started - const messages = await getMessages(); + const { locale } = await params; + + // Ensure locale is a valid string, fallback to 'en' + const supportedLocales = ['en', 'de']; + const localeStr = (typeof locale === 'string' ? locale : '').trim(); + const safeLocale = supportedLocales.includes(localeStr) ? localeStr : 'en'; + + let messages = {}; + try { + messages = await getMessages(); + } catch (error) { + console.error(`Failed to load messages for locale '${safeLocale}':`, error); + messages = {}; + } return ( - + - +
{children}