-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat: improve cookie banner visibility and engagement #8903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,23 +10,37 @@ import { dispatchConsentEvent } from "@/lib/analytics/consent-manager" | |
| /** | ||
| * GDPR-compliant cookie consent banner component | ||
| * Handles both the UI and consent event dispatching | ||
| * Enhanced with improved visibility and user interaction | ||
| */ | ||
| export function CookieConsentWrapper() { | ||
| const [cookieDomain, setCookieDomain] = useState<string | null>(null) | ||
| const [isVisible, setIsVisible] = useState(false) | ||
|
|
||
| useEffect(() => { | ||
| // Get the appropriate domain using tldts | ||
| if (typeof window !== "undefined") { | ||
| const domain = getDomain(window.location.hostname) | ||
| setCookieDomain(domain) | ||
| } | ||
|
|
||
| // Check if consent cookie exists | ||
| const cookieExists = document.cookie.includes(CONSENT_COOKIE_NAME) | ||
| if (!cookieExists) { | ||
| // Delay showing the banner slightly for better animation effect | ||
| const timer = setTimeout(() => { | ||
| setIsVisible(true) | ||
| }, 500) | ||
| return () => clearTimeout(timer) | ||
| } | ||
| }, []) | ||
|
|
||
| const handleAccept = () => { | ||
| setIsVisible(false) | ||
| dispatchConsentEvent(true) | ||
| } | ||
|
|
||
| const handleDecline = () => { | ||
| setIsVisible(false) | ||
| dispatchConsentEvent(false) | ||
| } | ||
|
|
||
|
|
@@ -37,75 +51,134 @@ export function CookieConsentWrapper() { | |
| : {} | ||
|
|
||
| const containerClasses = ` | ||
| fixed bottom-2 left-2 right-2 z-[999] | ||
| cookie-banner-container | ||
| fixed bottom-0 left-0 right-0 z-[999] | ||
| bg-black/95 dark:bg-white/95 | ||
| text-white dark:text-black | ||
| border-t-neutral-800 dark:border-t-gray-200 | ||
| border-t-2 border-t-neutral-700 dark:border-t-gray-300 | ||
| backdrop-blur-xl | ||
| border-t | ||
| shadow-2xl | ||
| font-semibold | ||
| rounded-t-lg | ||
| px-4 py-4 md:px-8 md:py-4 | ||
| px-4 py-5 md:px-8 md:py-6 | ||
| flex flex-wrap items-center justify-between gap-4 | ||
| text-sm font-sans | ||
| text-sm md:text-base font-sans | ||
| `.trim() | ||
|
|
||
| const buttonWrapperClasses = ` | ||
| flex | ||
| flex-row-reverse | ||
| items-center | ||
| gap-2 | ||
| gap-3 | ||
| `.trim() | ||
|
|
||
| const acceptButtonClasses = ` | ||
| bg-white text-black border-neutral-800 | ||
| dark:bg-black dark:text-white dark:border-gray-200 | ||
| hover:opacity-50 | ||
| transition-opacity | ||
| rounded-md | ||
| px-4 py-2 mr-2 | ||
| text-sm font-bold | ||
| cookie-accept-button | ||
| bg-green-600 text-white | ||
| dark:bg-green-500 dark:text-white | ||
| hover:bg-green-700 dark:hover:bg-green-600 | ||
| transition-all duration-200 | ||
| rounded-lg | ||
| px-6 py-2.5 md:px-8 md:py-3 | ||
| text-sm md:text-base font-bold | ||
| cursor-pointer | ||
| focus:outline-none focus:ring-2 focus:ring-offset-2 | ||
| shadow-lg hover:shadow-xl | ||
| focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 | ||
| transform hover:scale-105 | ||
| `.trim() | ||
|
|
||
| const declineButtonClasses = ` | ||
| dark:bg-white dark:text-black dark:border-gray-200 | ||
| bg-black text-white border-neutral-800 | ||
| hover:opacity-50 | ||
| border border-border | ||
| transition-opacity | ||
| rounded-md | ||
| px-4 py-2 | ||
| text-sm font-bold | ||
| dark:bg-gray-200 dark:text-gray-800 dark:border-gray-300 | ||
| bg-gray-800 text-gray-200 border-gray-600 | ||
| hover:bg-gray-700 dark:hover:bg-gray-300 | ||
| border-2 | ||
| transition-all duration-200 | ||
| rounded-lg | ||
| px-4 py-2.5 md:px-6 md:py-3 | ||
| text-sm md:text-base font-bold | ||
| cursor-pointer | ||
| focus:outline-none focus:ring-2 focus:ring-offset-2 | ||
| focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 | ||
| `.trim() | ||
|
|
||
| // Don't render anything if not visible | ||
| if (!isVisible) { | ||
| return null | ||
| } | ||
|
|
||
| return ( | ||
| <div role="banner" aria-label="Cookie consent banner" aria-live="polite"> | ||
| <ReactCookieConsent | ||
| location="bottom" | ||
| buttonText="Accept" | ||
| declineButtonText="Decline" | ||
| cookieName={CONSENT_COOKIE_NAME} | ||
| expires={365} | ||
| enableDeclineButton={true} | ||
| onAccept={handleAccept} | ||
| onDecline={handleDecline} | ||
| containerClasses={containerClasses} | ||
| buttonClasses={acceptButtonClasses} | ||
| buttonWrapperClasses={buttonWrapperClasses} | ||
| declineButtonClasses={declineButtonClasses} | ||
| extraCookieOptions={extraCookieOptions} | ||
| disableStyles={true} | ||
| ariaAcceptLabel={`Accept`} | ||
| ariaDeclineLabel={`Decline`}> | ||
| <div className="flex items-center gap-2"> | ||
| <Cookie className="size-5 hidden md:block" /> | ||
| <span>Like most of the internet, we use cookies. Are you OK with that?</span> | ||
| </div> | ||
| </ReactCookieConsent> | ||
| </div> | ||
| <> | ||
| {/* Backdrop overlay */} | ||
| <div | ||
| className="fixed inset-0 bg-black/30 dark:bg-black/20 z-[998] backdrop-blur-[2px]" | ||
| style={{ | ||
| animation: "fadeIn 0.3s ease-out", | ||
| }} | ||
| /> | ||
|
|
||
| {/* Banner */} | ||
| <div role="banner" aria-label="Cookie consent banner" aria-live="polite"> | ||
| <style | ||
| dangerouslySetInnerHTML={{ | ||
| __html: ` | ||
| @keyframes slideUp { | ||
| from { | ||
| transform: translateY(100%); | ||
| opacity: 0; | ||
| } | ||
| to { | ||
| transform: translateY(0); | ||
| opacity: 1; | ||
| } | ||
| } | ||
| @keyframes fadeIn { | ||
| from { | ||
| opacity: 0; | ||
| } | ||
| to { | ||
| opacity: 1; | ||
| } | ||
| } | ||
| @keyframes pulse { | ||
| 0%, 100% { | ||
| box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.4); | ||
| } | ||
| 50% { | ||
| box-shadow: 0 0 0 8px rgba(34, 197, 94, 0); | ||
| } | ||
| } | ||
|
Comment on lines
+140
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The global |
||
| .cookie-banner-container { | ||
| animation: slideUp 0.4s ease-out; | ||
| } | ||
| .cookie-accept-button { | ||
| animation: pulse 2s infinite; | ||
| } | ||
| `, | ||
| }} | ||
| /> | ||
| <ReactCookieConsent | ||
| location="bottom" | ||
| buttonText="Accept Cookies" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Internationalization: Consider wrapping all user-facing strings (e.g., 'Accept Cookies', 'Decline', and the banner message) in your i18n/translation function instead of hardcoding them. This ensures consistency and easier localization across Roo Code. This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX. |
||
| declineButtonText="Decline" | ||
| cookieName={CONSENT_COOKIE_NAME} | ||
| expires={365} | ||
| enableDeclineButton={true} | ||
| onAccept={handleAccept} | ||
| onDecline={handleDecline} | ||
| containerClasses={containerClasses} | ||
| buttonClasses={acceptButtonClasses} | ||
| buttonWrapperClasses={buttonWrapperClasses} | ||
| declineButtonClasses={declineButtonClasses} | ||
| extraCookieOptions={extraCookieOptions} | ||
| disableStyles={true} | ||
| ariaAcceptLabel="Accept all cookies" | ||
| ariaDeclineLabel="Decline non-essential cookies"> | ||
| <div className="flex items-center gap-3"> | ||
| <Cookie className="size-6 md:size-7 flex-shrink-0" /> | ||
| <span className="leading-relaxed"> | ||
| Like most of the internet, we use cookies to enhance your experience. Are you OK with that? | ||
| </span> | ||
| </div> | ||
| </ReactCookieConsent> | ||
| </div> | ||
| </> | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cookie check using string includes is fragile and could produce false positives if another cookie's value contains 'roo-code-cookie-consent' as a substring. The codebase already imports
getCookieConsentValuefrom react-cookie-consent in the consent-manager module. Consider using that function or implementing proper cookie parsing to check for the exact cookie name.