Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 121 additions & 48 deletions apps/web-roo-code/src/components/CookieConsentWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Author

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 getCookieConsentValue from react-cookie-consent in the consent-manager module. Consider using that function or implementing proper cookie parsing to check for the exact 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)
}

Expand All @@ -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
Copy link
Author

Choose a reason for hiding this comment

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

The global pulse keyframe definition will override Tailwind's built-in animate-pulse utility, potentially breaking any existing usage of that class across the application. Consider renaming this to something more specific like cookiePulse to avoid conflicts with Tailwind's animation system.

.cookie-banner-container {
animation: slideUp 0.4s ease-out;
}
.cookie-accept-button {
animation: pulse 2s infinite;
}
`,
}}
/>
<ReactCookieConsent
location="bottom"
buttonText="Accept Cookies"
Copy link

Choose a reason for hiding this comment

The 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>
</>
)
}