From f385d43d5d29fd6a8f36d17094fe7a96eb8fefea Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sat, 18 Jul 2026 17:52:40 +0000 Subject: [PATCH] feat: redirect to AI consent settings when consent is missing - requestAiConsent now redirects to /settings/general?highlight=aiConsent instead of opening the modal when called outside the settings page - Settings page highlights the consent section and scrolls to it - Adds consent.ai.settingsBanner i18n key (fr/en) - Keeps inline modal on /settings/general so the grant button still works --- .../general/general-settings-client.tsx | 34 +++++++++++++++++-- memento-note/components/dashboard-view.tsx | 8 +++-- .../components/legal/ai-consent-provider.tsx | 18 ++++++++-- .../lib/consent/ai-consent-redirect.ts | 24 +++++++++++++ memento-note/locales/en.json | 3 +- memento-note/locales/fr.json | 3 +- 6 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 memento-note/lib/consent/ai-consent-redirect.ts diff --git a/memento-note/app/(main)/settings/general/general-settings-client.tsx b/memento-note/app/(main)/settings/general/general-settings-client.tsx index bbb05d5..447393b 100644 --- a/memento-note/app/(main)/settings/general/general-settings-client.tsx +++ b/memento-note/app/(main)/settings/general/general-settings-client.tsx @@ -1,11 +1,11 @@ 'use client' -import { useState } from 'react' +import { useState, useEffect, useRef } from 'react' import Link from 'next/link' import { useLanguage } from '@/lib/i18n' import { updateAISettings } from '@/app/actions/ai-settings' import { toast } from 'sonner' -import { useRouter } from 'next/navigation' +import { useRouter, useSearchParams } from 'next/navigation' import { Globe, Bell, Shield, Brain, HelpCircle } from 'lucide-react' import { motion } from 'motion/react' import { openCookiePreferences } from '@/lib/consent/cookie-consent' @@ -27,6 +27,20 @@ export function GeneralSettingsClient({ initialSettings }: GeneralSettingsClient const { t, setLanguage: setContextLanguage } = useLanguage() const { hasAiConsent, revokeConsent, requestAiConsent } = useAiConsent() const router = useRouter() + const searchParams = useSearchParams() + const aiConsentSectionRef = useRef(null) + const highlightAiConsent = searchParams?.get('highlight') === 'aiConsent' + const [showConsentHighlight, setShowConsentHighlight] = useState(highlightAiConsent) + + useEffect(() => { + if (highlightAiConsent && aiConsentSectionRef.current) { + setShowConsentHighlight(true) + aiConsentSectionRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' }) + const timer = setTimeout(() => setShowConsentHighlight(false), 4000) + return () => clearTimeout(timer) + } + }, [highlightAiConsent]) + const [language, setLanguage] = useState(initialSettings.preferredLanguage || 'auto') const [emailNotifications, setEmailNotifications] = useState(initialSettings.emailNotifications ?? false) const [desktopNotifications, setDesktopNotifications] = useState(initialSettings.desktopNotifications ?? false) @@ -76,6 +90,12 @@ export function GeneralSettingsClient({ initialSettings }: GeneralSettingsClient {t('generalSettings.description')} + {highlightAiConsent && ( +
+ {t('consent.ai.settingsBanner')} +
+ )} +
@@ -202,7 +222,15 @@ export function GeneralSettingsClient({ initialSettings }: GeneralSettingsClient
-
+
diff --git a/memento-note/components/dashboard-view.tsx b/memento-note/components/dashboard-view.tsx index 176a431..ebd9a91 100644 --- a/memento-note/components/dashboard-view.tsx +++ b/memento-note/components/dashboard-view.tsx @@ -6,6 +6,7 @@ import { useReducedMotion } from 'motion/react' import { Inbox, Send, Bell, Mail } from 'lucide-react' import { useLanguage } from '@/lib/i18n' import { useAiConsent } from '@/components/legal/ai-consent-provider' +import { redirectToAiConsentSettings } from '@/lib/consent/ai-consent-redirect' import { createNote } from '@/app/actions/notes' import { emitNoteChange } from '@/lib/note-change-sync' import { toast } from 'sonner' @@ -519,7 +520,10 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) { try { const res = await fetch('/api/ai/echo') const json = await res.json() - if (res.status === 403 && json.code === 'ai_consent_required') { await requestAiConsent(); return } + if (res.status === 403 && json.code === 'ai_consent_required') { + redirectToAiConsentSettings(router) + return + } if (!res.ok) throw new Error(json.error) if (json.insight) { await reloadBriefingAndPaths() @@ -533,7 +537,7 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) { } finally { setEchoRefreshing(false) } - }, [requestAiConsent, reloadBriefingAndPaths, t]) + }, [requestAiConsent, reloadBriefingAndPaths, t, router]) const handleEnableAi = useCallback(async () => { await requestAiConsent() diff --git a/memento-note/components/legal/ai-consent-provider.tsx b/memento-note/components/legal/ai-consent-provider.tsx index bb48495..7ad51ff 100644 --- a/memento-note/components/legal/ai-consent-provider.tsx +++ b/memento-note/components/legal/ai-consent-provider.tsx @@ -2,7 +2,9 @@ import { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react' import { useSession } from 'next-auth/react' +import { useRouter } from 'next/navigation' import { getLocalStorageAiConsent, setLocalStorageAiConsent, removeLocalStorageAiConsent } from '@/lib/consent/ai-consent-client' +import { redirectToAiConsentSettings } from '@/lib/consent/ai-consent-redirect' import { updateAISettings } from '@/app/actions/ai-settings' import { AiConsentModal } from './ai-consent-modal' import { toast } from 'sonner' @@ -24,6 +26,7 @@ interface AiConsentProviderProps { export function AiConsentProvider({ children, initialPersistentConsent = false }: AiConsentProviderProps) { const { data: session, update: updateSession } = useSession() const { t } = useLanguage() + const router = useRouter() const [persistentConsent, setPersistentConsent] = useState(false) const [sessionConsent, setSessionConsent] = useState(false) @@ -60,11 +63,22 @@ export function AiConsentProvider({ children, initialPersistentConsent = false } return Promise.resolve(true) } - setModalOpen(true) + // On settings page we keep the inline modal; elsewhere redirect to settings + // so the user sees exactly where to grant consent. + if (typeof window !== 'undefined' && window.location.pathname.startsWith('/settings/general')) { + setModalOpen(true) + return new Promise((resolve) => { + pendingResolveRef.current = resolve + }) + } + + redirectToAiConsentSettings(router, { replace: false }) return new Promise((resolve) => { pendingResolveRef.current = resolve + pendingResolveRef.current(false) + pendingResolveRef.current = null }) - }, [hasAiConsent]) + }, [hasAiConsent, router]) const handleConfirm = async (remember: boolean) => { setModalOpen(false) diff --git a/memento-note/lib/consent/ai-consent-redirect.ts b/memento-note/lib/consent/ai-consent-redirect.ts new file mode 100644 index 0000000..f0bb41a --- /dev/null +++ b/memento-note/lib/consent/ai-consent-redirect.ts @@ -0,0 +1,24 @@ +import type { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime' + +/** + * Redirects the user to General Settings with the AI consent section highlighted. + * Call this when an AI feature is blocked because the user has not granted consent. + */ +export function redirectToAiConsentSettings( + router: AppRouterInstance | ReturnType, + options?: { replace?: boolean } +) { + const url = '/settings/general?highlight=aiConsent' + if (options?.replace) { + router.replace(url) + } else { + router.push(url) + } +} + +/** + * Returns the settings URL that highlights the AI consent section. + */ +export function getAiConsentSettingsUrl() { + return '/settings/general?highlight=aiConsent' +} diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json index 2283f15..1d8d6e8 100644 --- a/memento-note/locales/en.json +++ b/memento-note/locales/en.json @@ -3807,7 +3807,8 @@ "revokedToast": "AI processing consent successfully revoked.", "complianceBadge": "GDPR compliance", "auditFailed": "Could not record your consent. Please try again.", - "requiredToast": "Enable AI processing in Settings → AI to use this feature." + "requiredToast": "Enable AI processing in Settings → AI to use this feature.", + "settingsBanner": "To use this feature, enable AI processing below and click « Grant consent »." } }, "account": { diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json index 99007fb..ffd7136 100644 --- a/memento-note/locales/fr.json +++ b/memento-note/locales/fr.json @@ -3813,7 +3813,8 @@ "revokedToast": "Consentement IA révoqué avec succès.", "complianceBadge": "Conformité RGPD", "auditFailed": "Impossible d'enregistrer votre consentement. Réessayez.", - "requiredToast": "Activez le traitement IA dans Paramètres → IA pour utiliser cette fonctionnalité." + "requiredToast": "Activez le traitement IA dans Paramètres → IA pour utiliser cette fonctionnalité.", + "settingsBanner": "Pour utiliser cette fonctionnalité, activez le traitement IA ci-dessous puis cliquez sur « Accorder le consentement »." } }, "account": {