Skip to content

feat(cookie-consent): add GDPR cookie consent banner - #209

Merged
ayoub3bidi merged 3 commits into
developfrom
feat/cookie-consent-banner
Jul 23, 2026
Merged

feat(cookie-consent): add GDPR cookie consent banner#209
ayoub3bidi merged 3 commits into
developfrom
feat/cookie-consent-banner

Conversation

@ayoub3bidi

@ayoub3bidi ayoub3bidi commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Contribution workflow

  • Base branch is develop: This PR targets develop, not main.
  • Guidelines and docs: I have read CONTRIBUTING.md and the docs relevant to my change.
  • This template: I kept the PR template structure and filled in the sections below that apply to this change.

Description

Adds a GDPR-compliant cookie consent banner that gates PostHog analytics behind explicit user consent. Users can Accept All or Decline analytics tracking. Consent is persisted in localStorage and can be revoked via a "Cookie Preferences" link in the footer. DNT and Global Privacy Control signals auto-deny without showing the banner.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Style/UI improvement
  • Performance improvement
  • Test addition or improvement
  • Chore (maintenance, dependencies, etc.)

Related Issues

N/A

Changes Made

  • Added ConsentContext with localStorage persistence, DNT/GPC auto-deny, and grant/deny/reset actions
  • Added CookieConsentBanner component with Accept All / Decline All buttons, framer-motion animation, dark mode glass morphism, and RTL support
  • Modified PostHogProvider to accept analytics prop and defer initPostHog() until consent is granted
  • Extracted AppShell component from main.jsx for consent-aware provider wiring
  • Created useConsent hook in dedicated hooks/ file for react-refresh compliance
  • Added "Cookie Preferences" link to Footer that calls resetConsent() to reopen the banner
  • Updated privacy policy to reflect the consent banner
  • Added i18n keys for consent UI in en/fr/ar (including RTL Arabic)

Algorithm Details (if applicable)

N/A

Testing

  • All existing tests pass (pnpm test:run)
  • New tests added for new functionality
  • Manual testing completed
  • Cross-browser testing (if UI changes)

Test Results

 ✓ src/contexts/ConsentContext.test.jsx (8 tests)
 ✓ src/components/CookieConsentBanner.test.jsx (7 tests)
 ✓ src/components/Footer.test.jsx (6 tests)

 Test Files  3 passed (3)
      Tests  21 passed (21)

Screenshots/GIFs

Before After
PostHog initializes immediately on page load PostHog deferred until user accepts analytics
No consent UI Bottom banner with Accept All / Decline All + privacy policy link
No way to reopen consent Footer "Cookie Preferences" link reopens banner

Code Quality

  • Code follows the project's coding standards
  • ESLint passes (pnpm lint) - 0 errors, 2 pre-existing warnings
  • Prettier formatting applied
  • No console errors or warnings
  • Code is properly documented with JSDoc

Performance Impact

  • No performance impact

Accessibility

  • Keyboard navigation works correctly
  • Screen reader compatibility maintained
  • Color contrast meets WCAG guidelines
  • Focus indicators are visible

Breaking Changes

  • None

Checklist

  • I have completed the Contribution workflow checklist at the top of this template
  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Additional Notes

  • Zero new npm dependencies - custom banner for full RTL/i18n/dark mode control
  • Provider tree order: ThemeProvider -> AuthProvider -> ConsentProvider -> AppShell(PostHogProvider -> BrowserRouter)
  • src/contexts/ConsentContext.jsx manages consent state, src/hooks/useConsent.js exposes the hook

Reviewer Guidelines:

  • Check that all tests pass
  • Verify code follows project standards
  • Test the changes locally
  • Review for security implications
  • Ensure documentation is updated

Summary by CodeRabbit

  • New Features

    • Added a cookie consent banner with localized messaging, privacy-policy access, and accept/decline options.
    • Added cookie preference controls in the footer, allowing consent to be changed later.
    • Analytics now respects cookie consent and privacy signals such as Do Not Track and Global Privacy Control.
    • Added Arabic, English, and French translations for consent-related interface text.
  • Documentation

    • Updated the privacy policy to explain analytics cookie consent.

- ConsentContext with localStorage persistence, DNT/GPC auto-deny
- CookieConsentBanner with Accept All / Decline All, framer-motion animation
- PostHogProvider gated behind analytics consent (deferred init)
- AppShell component extracted for consent-aware provider wiring
- useConsent hook in dedicated hooks/ file (react-refresh compliant)
- Footer 'Cookie Preferences' link to reopen consent
- Privacy policy updated to reflect consent banner
- i18n keys in en/fr/ar (including RTL Arabic support)
- 21 tests across ConsentContext, CookieConsentBanner, Footer
@github-actions github-actions Bot added style Improve styling, design, and animation tests labels Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 01b5b904-bd54-4648-bc6d-ab6ec5fcb78c

📥 Commits

Reviewing files that changed from the base of the PR and between a430c7b and adc4b24.

📒 Files selected for processing (6)
  • src/components/Footer.test.jsx
  • src/contexts/ConsentContext.jsx
  • src/contexts/ConsentContext.test.jsx
  • src/hooks/useConsent.js
  • src/i18n/locales/ar/translation.json
  • src/providers/PostHogProvider.jsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/providers/PostHogProvider.jsx
  • src/components/Footer.test.jsx
  • src/contexts/ConsentContext.test.jsx
  • src/i18n/locales/ar/translation.json

📝 Walkthrough

Walkthrough

Introduces persisted analytics consent with privacy-signal handling, an animated localized consent banner, footer preference reset, consent-aware app composition, and deferred PostHog initialization.

Changes

Consent-gated analytics

Layer / File(s) Summary
Consent state and hook
src/contexts/ConsentContext.jsx, src/hooks/useConsent.js, src/contexts/ConsentContext.test.jsx
Adds localStorage-backed consent state, grant/deny/reset actions, privacy-signal handling, analytics status, and tests.
Consent banner and localized copy
src/components/CookieConsentBanner.jsx, src/components/CookieConsentBanner.test.jsx, src/i18n/locales/*/translation.json
Adds an animated, accessible consent dialog with localized actions, privacy navigation, persistence behavior, and coverage.
Application and analytics wiring
src/main.jsx, src/AppShell.jsx, src/providers/PostHogProvider.jsx
Moves app composition into AppShell and initializes or toggles PostHog capture based on analytics consent.
Preference reset and policy updates
src/components/Footer.jsx, src/components/Footer.test.jsx, src/content/legal/privacy.en.js
Adds a cookie-preferences reset action and updates privacy-policy consent statements.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ConsentProvider
  participant AppShell
  participant PostHogProvider
  participant PostHog
  ConsentProvider->>AppShell: isAnalyticsAllowed
  AppShell->>PostHogProvider: analytics prop
  PostHogProvider->>PostHog: initialize or toggle capture
Loading

Possibly related PRs

Suggested labels: config

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a GDPR cookie consent banner and related consent flow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cookie-consent-banner

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
src/i18n/locales/ar/translation.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/components/Footer.test.jsx (1)

84-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test the reset action, not just its label.

Seed stored consent, click the button, and assert the stored decision is cleared and the consent state is reopened. The current test would pass if the button lost its onClick handler.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Footer.test.jsx` around lines 84 - 89, Update the “renders
cookie preferences button” test to seed stored consent, click the cookie
preferences button, and assert the stored consent decision is cleared and the
consent state is reopened. Retain the existing label assertion while verifying
the button’s reset behavior rather than only its presence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Footer.jsx`:
- Around line 317-325: Update resetConsent in the consent provider so reopening
preferences re-evaluates the active DNT/GPC privacy signal before allowing
consent changes. If the signal is active, keep analytics denied and prevent the
reopened banner from enabling acceptance; otherwise preserve the existing reset
behavior.

In `@src/contexts/ConsentContext.jsx`:
- Around line 84-100: Update ConsentContext initialization to evaluate
hasPrivacySignal() synchronously before honoring stored consent; when active,
force analytics denial, persist it with writeConsent, and hide the banner even
if stored consent allows analytics. Update resetConsent to preserve this forced
denial and prevent reopening a banner that would allow re-enabling analytics
while the signal remains active. Add regressions covering both initial load with
stored approval and resetConsent under an active DNT/GPC signal.

In `@src/contexts/ConsentContext.test.jsx`:
- Around line 78-96: Isolate the “auto-denies when Global Privacy Control is
set” test by resetting navigator.doNotTrack to a non-active value such as '0'
before rendering useConsent, or by restoring navigator properties between tests.
Ensure the assertion exercises the globalPrivacyControl branch rather than the
preceding DNT state.

In `@src/i18n/locales/ar/translation.json`:
- Line 1029: Update the Arabic consent message in the locale entry keyed by
“message” to explicitly state that cookies are used for analytics or optional
analytics tracking, while preserving the existing acceptance/rejection wording
and Arabic localization.

In `@src/providers/PostHogProvider.jsx`:
- Around line 17-22: Update PostHogProvider’s useEffect to apply the configured
consent flow on every analytics transition, opting in when consent is granted
and calling opt_out_capturing (or using opt_out_capturing_by_default) when it is
declined or reset. Preserve existing DNT and PostHog configuration behavior, and
add coverage for accept-to-decline and accept-to-reset transitions.

---

Nitpick comments:
In `@src/components/Footer.test.jsx`:
- Around line 84-89: Update the “renders cookie preferences button” test to seed
stored consent, click the cookie preferences button, and assert the stored
consent decision is cleared and the consent state is reopened. Retain the
existing label assertion while verifying the button’s reset behavior rather than
only its presence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dbb995f9-2f64-434a-810c-c735051856cf

📥 Commits

Reviewing files that changed from the base of the PR and between 0b804d9 and a430c7b.

📒 Files selected for processing (14)
  • src/AppShell.jsx
  • src/components/CookieConsentBanner.jsx
  • src/components/CookieConsentBanner.test.jsx
  • src/components/Footer.jsx
  • src/components/Footer.test.jsx
  • src/content/legal/privacy.en.js
  • src/contexts/ConsentContext.jsx
  • src/contexts/ConsentContext.test.jsx
  • src/hooks/useConsent.js
  • src/i18n/locales/ar/translation.json
  • src/i18n/locales/en/translation.json
  • src/i18n/locales/fr/translation.json
  • src/main.jsx
  • src/providers/PostHogProvider.jsx

Comment thread src/components/Footer.jsx
Comment thread src/contexts/ConsentContext.jsx Outdated
Comment thread src/contexts/ConsentContext.test.jsx
Comment thread src/i18n/locales/ar/translation.json Outdated
Comment thread src/providers/PostHogProvider.jsx
…utside provider

Footer uses useConsent() for the Cookie Preferences link. When other
components/pages render Footer without ConsentProvider (e.g. in tests),
the hook now returns safe defaults (analytics denied, no banner) instead
of throwing. This fixes ProComingSoonPage.test.jsx and any other test
that renders a page containing Footer.
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Preview for Bayan Flow Staging ready!

Name Link
🔨 Latest commit adc4b24
🔍 Latest deploy log https://github.com/ayoub3bidi/bayan-flow/actions/runs/29989961865
😎 Deploy Preview https://pr-209-bayan-flow-staging.ayoub3bidi.workers.dev
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

Preview alias pr-209 on the staging worker. Updates automatically with new commits.

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.00000% with 28 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/AppShell.jsx 6.25% 15 Missing ⚠️
src/providers/PostHogProvider.jsx 0.00% 13 Missing ⚠️

📢 Thoughts on this report? Let us know!

- ConsentContext: DNT/GPC now overrides stored consent on init and resetConsent
- PostHogProvider: opt in/out on consent transitions, not just init on grant
- ConsentContext tests: isolate GPC test from DNT, add DNT-overrides-stored and resetConsent-persists-denial tests
- Arabic locale: clarify analytics purpose in consent message
- Footer test: verify reset clears stored consent
@ayoub3bidi
ayoub3bidi merged commit 25e3283 into develop Jul 23, 2026
15 checks passed
@ayoub3bidi
ayoub3bidi deleted the feat/cookie-consent-banner branch July 23, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

style Improve styling, design, and animation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant