feat: add RGPD info popup with header trigger button

This commit is contained in:
devcodetools committed 2026-09-13 18:31:11 +02:00
1 parent e5220a4c4e
commit 7655cb5b69
4 files changed
+86

No files matched your search

+12
View File
@@ -4,11 +4,13 @@ import { useNotes } from './hooks/useNotes'
import NoteList from './components/NoteList'
import NoteEditor from './components/NoteEditor'
import FloatingButton from './components/FloatingButton'
import RgpdInfo from './components/RgpdInfo'
function App() {
const { notes, addNote, updateNote, deleteNote } = useNotes()
const [selectedId, setSelectedId] = useState<number | null>(null)
const [creating, setCreating] = useState(false)
const [showRgpd, setShowRgpd] = useState(false)
const selectedNote =
selectedId === null ? null : (notes.find((note) => note.id === selectedId) ?? null)
@@ -51,6 +53,14 @@ function App() {
</button>
) : null}
<h1 className="text-lg font-semibold">Notes</h1>
<button
type="button"
onClick={() => setShowRgpd(true)}
aria-label="Informations sur la protection des données"
className="ml-auto flex items-center justify-center w-11 h-11 rounded-full text-xl font-semibold text-gray-700 hover:bg-gray-100 active:bg-gray-200"
>
?
</button>
</div>
</header>
@@ -76,6 +86,8 @@ function App() {
</section>
{!creating && !selectedNote && <FloatingButton onClick={createNote} />}
<RgpdInfo open={showRgpd} onClose={() => setShowRgpd(false)} />
</main>
)
}
+31
View File
@@ -0,0 +1,31 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import RgpdInfo from './RgpdInfo'
describe('RgpdInfo', () => {
it('renders nothing when closed', () => {
const { container } = render(<RgpdInfo open={false} onClose={vi.fn()} />)
expect(container).toBeEmptyDOMElement()
})
it('renders the GDPR information when open', () => {
render(<RgpdInfo open onClose={vi.fn()} />)
expect(screen.getByRole('dialog', { name: 'Protection des données (RGPD)' })).toBeInTheDocument()
expect(screen.getByText('localStorage', { exact: false })).toBeInTheDocument()
expect(
screen.getByText(/ne sont jamais partagées avec des tiers/i),
).toBeInTheDocument()
expect(screen.getByText(/aucune transmission vers un serveur externe/i)).toBeInTheDocument()
})
it('calls onClose when the close button is clicked', () => {
const onClose = vi.fn()
render(<RgpdInfo open onClose={onClose} />)
screen.getByRole('button', { name: 'Fermer' }).click()
expect(onClose).toHaveBeenCalled()
})
})
+39
View File
@@ -0,0 +1,39 @@
import type { RgpdInfoProps } from '../interfaces/rgpd-info'
function RgpdInfo({ open, onClose }: RgpdInfoProps) {
if (!open) return null
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
<div
role="dialog"
aria-modal="true"
aria-labelledby="rgpd-title"
className="flex w-full max-w-sm flex-col gap-4 rounded-xl bg-white p-5 shadow-xl"
>
<h2 id="rgpd-title" className="text-lg font-semibold">
Protection des données (RGPD)
</h2>
<ul className="flex list-disc flex-col gap-2 pl-5 text-sm text-gray-600">
<li>
Vos données sont stockées uniquement dans le
<span className="font-medium text-gray-800">&nbsp;stockage local</span> de votre navigateur.
</li>
<li>Elles ne sont jamais partagées avec des tiers.</li>
<li>Aucune transmission vers un serveur externe.</li>
</ul>
<div className="flex justify-end">
<button
type="button"
onClick={onClose}
className="rounded-lg bg-blue-700 px-5 py-3 text-white hover:bg-blue-800 active:bg-blue-900"
>
Fermer
</button>
</div>
</div>
</div>
)
}
export default RgpdInfo
+4
View File
@@ -0,0 +1,4 @@
export interface RgpdInfoProps {
open: boolean
onClose: () => void
}