fix(dashboard): update Recapt integration to load globally and streamline widget rendering (#592)

This commit is contained in:
Mattsson
2026-05-28 16:41:44 +02:00
committed by GitHub
parent a9aff5a120
commit cb7eac90b1
3 changed files with 19 additions and 29 deletions
+5 -11
View File
@@ -5,8 +5,6 @@ import DashboardNav from '@/components/dashboard/DashboardNav'
import { MainContainer } from '@/components/dashboard/MainContainer'
import CompanyTabSync from '@/components/dashboard/CompanyTabSync'
import { RecaptIdentify } from '@/components/RecaptIdentify'
import { RecaptLoader } from '@/components/RecaptLoader'
import { RecaptHideWidget } from '@/components/RecaptHideWidget'
import { AgentSheetProvider } from '@/components/agent/AgentSheetProvider'
import AgentTrigger from '@/components/agent/AgentTrigger'
import CommandPalette from '@/components/common/CommandPalette'
@@ -282,15 +280,11 @@ export default async function DashboardLayout({
<CommandPalette />
</div>
{!isSandbox && (
<>
<RecaptLoader />
<RecaptHideWidget />
<RecaptIdentify
userId={user.id}
email={user.email}
displayName={settings?.company_name || undefined}
/>
</>
<RecaptIdentify
userId={user.id}
email={user.email}
displayName={settings?.company_name || undefined}
/>
)}
</AgentSheetProvider>
</CompanyProvider>
+4
View File
@@ -6,6 +6,8 @@ import { NextIntlClientProvider } from "next-intl";
import { getLocale, getMessages } from "next-intl/server";
import { Toaster } from "@/components/ui/toaster";
import { ThemeProvider } from "@/components/theme-provider";
import { RecaptLoader } from "@/components/RecaptLoader";
import { RecaptHideWidget } from "@/components/RecaptHideWidget";
import { ensureInitialized } from "@/lib/init";
import { getBranding } from "@/lib/branding/service";
import "./globals.css";
@@ -70,6 +72,7 @@ export default async function RootLayout({
<html lang={locale} suppressHydrationWarning className={`${geistSans.variable} ${geistMono.variable} ${hedvigSerif.variable}`}>
<head>
<link rel="apple-touch-icon" href={branding.appleTouchIconPath} />
<RecaptLoader />
</head>
<body
className="antialiased"
@@ -83,6 +86,7 @@ export default async function RootLayout({
>
{children}
<Toaster />
<RecaptHideWidget />
</ThemeProvider>
</NextIntlClientProvider>
<Script src="/sw-register.js" strategy="afterInteractive" />
+10 -18
View File
@@ -1,31 +1,23 @@
'use client'
import Script from 'next/script'
/**
* Loads the Recapt SDK for authenticated dashboard users only.
* Loads the Recapt SDK globally.
*
* Privacy guard rails:
* - Mounted inside the dashboard layout, so the script never loads on
* public pages (login, register, privacy policy, marketing). This
* prevents pre-consent IP/fingerprint collection on those routes.
* - The public key is sourced from NEXT_PUBLIC_RECAPT_PUBLIC_KEY so
* hosted and self-hosted deployments can each supply their own key
* (or disable Recapt entirely by leaving it unset).
* - data-persist / data-enable-user-comments are intentionally omitted
* from the default tag. Persistent cross-session tracking and
* unstructured free-text capture are opt-in product decisions, not
* defaults (GDPR Art. 25 — privacy by default).
* Renders a plain <script> tag (not next/script) so it lands in <head> and
* runs as early as possible — that's what the SDK needs to capture full
* session replays. The public key is sourced from NEXT_PUBLIC_RECAPT_PUBLIC_KEY
* so hosted and self-hosted deployments can each supply their own key (or
* disable Recapt entirely by leaving it unset).
*/
export function RecaptLoader() {
const publicKey = process.env.NEXT_PUBLIC_RECAPT_PUBLIC_KEY
if (!publicKey) return null
return (
<Script
<script
src="https://cdn.recapt.app/browser/glimt.js"
strategy="afterInteractive"
async
data-public-key={publicKey}
data-persist=""
data-enable-user-comments=""
/>
)
}