feat(notes): liens internes, onglet Réseau, living blocks et consentement IA
Some checks failed
CI / Lint, Test & Build (push) Failing after 1m19s
CI / Deploy production (on server) (push) Has been skipped

Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-05-24 14:27:29 +00:00
parent 077e665dfc
commit e2672cd2c2
323 changed files with 20670 additions and 42431 deletions

View File

@@ -0,0 +1,173 @@
'use client'
import { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react'
import { useSession } from 'next-auth/react'
import { getLocalStorageAiConsent, setLocalStorageAiConsent, removeLocalStorageAiConsent } from '@/lib/consent/ai-consent-client'
import { updateAISettings } from '@/app/actions/ai-settings'
import { AiConsentModal } from './ai-consent-modal'
import { toast } from 'sonner'
import { useLanguage } from '@/lib/i18n'
interface AiConsentContextType {
hasAiConsent: boolean
requestAiConsent: () => Promise<boolean>
revokeConsent: () => Promise<void>
}
const AiConsentContext = createContext<AiConsentContextType | undefined>(undefined)
interface AiConsentProviderProps {
children: React.ReactNode
initialPersistentConsent?: boolean
}
export function AiConsentProvider({ children, initialPersistentConsent = false }: AiConsentProviderProps) {
const { data: session, update: updateSession } = useSession()
const { t } = useLanguage()
const [persistentConsent, setPersistentConsent] = useState(false)
const [sessionConsent, setSessionConsent] = useState(false)
const [modalOpen, setModalOpen] = useState(false)
const [hydrated, setHydrated] = useState(false)
const pendingResolveRef = useRef<((value: boolean) => void) | null>(null)
useEffect(() => {
const local = getLocalStorageAiConsent()
if (local || initialPersistentConsent) {
setPersistentConsent(true)
if (initialPersistentConsent && !local) {
setLocalStorageAiConsent(true)
}
}
setHydrated(true)
}, [initialPersistentConsent])
useEffect(() => {
if (session?.aiSessionConsent === true) {
setSessionConsent(true)
} else if (session?.aiSessionConsent === false) {
setSessionConsent(false)
}
}, [session?.aiSessionConsent])
const hasAiConsent =
hydrated &&
(persistentConsent || sessionConsent || session?.aiSessionConsent === true)
const requestAiConsent = useCallback((): Promise<boolean> => {
if (hasAiConsent) {
return Promise.resolve(true)
}
setModalOpen(true)
return new Promise<boolean>((resolve) => {
pendingResolveRef.current = resolve
})
}, [hasAiConsent])
const handleConfirm = async (remember: boolean) => {
setModalOpen(false)
try {
const res = await fetch('/api/user/ai-consent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ consent: true, remember }),
})
if (!res.ok) {
toast.error(t('consent.ai.auditFailed') || 'Impossible denregistrer votre consentement. Réessayez.')
pendingResolveRef.current?.(false)
pendingResolveRef.current = null
return
}
if (remember) {
setLocalStorageAiConsent(true)
setPersistentConsent(true)
try {
await updateAISettings({ aiProcessingConsent: true })
} catch (e) {
console.error('[AiConsentProvider] Failed to sync consent to DB:', e)
}
} else {
await updateSession({ aiSessionConsent: true })
setSessionConsent(true)
}
pendingResolveRef.current?.(true)
pendingResolveRef.current = null
} catch (e) {
console.error('[AiConsentProvider] Failed to log consent audit:', e)
toast.error(t('consent.ai.auditFailed') || 'Impossible denregistrer votre consentement. Réessayez.')
pendingResolveRef.current?.(false)
pendingResolveRef.current = null
}
}
const handleClose = () => {
setModalOpen(false)
toast.warning(t('consent.ai.aborted') || 'Traitement IA annulé (consentement refusé).')
pendingResolveRef.current?.(false)
pendingResolveRef.current = null
}
const revokeConsent = async () => {
removeLocalStorageAiConsent()
setPersistentConsent(false)
setSessionConsent(false)
try {
await fetch('/api/user/ai-consent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ consent: false, remember: true }),
})
} catch (e) {
console.error('[AiConsentProvider] Failed to log revocation:', e)
}
try {
await updateSession({ aiSessionConsent: false })
} catch (e) {
console.error('[AiConsentProvider] Failed to clear session consent:', e)
}
if (session?.user) {
try {
await updateAISettings({ aiProcessingConsent: false })
} catch (e) {
console.error('[AiConsentProvider] Failed to sync revocation to DB:', e)
}
}
toast.success(t('consent.ai.revokedToast') || 'Consentement IA révoqué avec succès.')
}
return (
<AiConsentContext.Provider
value={{
hasAiConsent,
requestAiConsent,
revokeConsent,
}}
>
{children}
<AiConsentModal
open={modalOpen}
onClose={handleClose}
onConfirm={handleConfirm}
/>
</AiConsentContext.Provider>
)
}
export function useAiConsent() {
const context = useContext(AiConsentContext)
if (context === undefined) {
throw new Error('useAiConsent must be used within an AiConsentProvider')
}
return context
}