-
Notifications
You must be signed in to change notification settings - Fork 530
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
chore: Added Cookie banner to Marketing website #2826
base: main
Are you sure you want to change the base?
Conversation
- Updated pnpm-lock.yaml to version 9.0 and adjusted package versions for dependencies. - Added @koroflow/core-react for consent management. - Implemented ConsentManagerProvider in layout.tsx to manage user consent. - Created CookieBanner and ConsentCustomizationDialog components for user consent interactions. - Enhanced button components to support onKeyDown and onKeyUp events. - Updated footer to include a link for changing consent preferences. - Integrated consent checks in PostHogPageView for analytics tracking based on user consent.
|
Warning Rate limit exceeded@BurnedChris has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 11 minutes and 3 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe pull request introduces a comprehensive consent management system for the web application. It adds new components like Changes
Possibly related PRs
Suggested labels
Suggested reviewers
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Thank you for following the naming conventions for pull request titles! 🙏 |
@BurnedChris is attempting to deploy a commit to the Unkey Team on Vercel. A member of the Team first needs to authorize it. |
I don't know why the pnpm.lock has changed so much maybe it was formatting problem? |
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.
Actionable comments posted: 4
🔭 Outside diff range comments (2)
apps/www/providers/posthog/PostHogPageView.tsx (1)
Line range hint
15-24
: Add error handling for PostHog tracking calls.The PostHog capture calls should be wrapped in try-catch blocks to handle potential failures gracefully.
if (pathname && posthog && consents.measurement) { + try { posthog.capture("$pageleave"); let url = window.origin + pathname; if (searchParams.toString()) { url = `${url}?${searchParams.toString()}`; } posthog.capture("$pageview", { $current_url: url, }); + } catch (error) { + console.error("Failed to capture analytics:", error); + } }apps/www/components/button.tsx (1)
Line range hint
16-27
: Convert PrimaryButton to use button element for accessibility.Using a div for a button is not accessible. Convert to a proper button element and remove the TODO comment.
-// TODO: use <button> not <div> export const PrimaryButton: React.FC<Props> = ({ className, IconLeft, label, IconRight, shiny = false, ...props }) => { return ( - //@ts-expect-error added props spread to add onClick, onKeyDown, onKeyUp - <div className="relative group/button" {...props}> + <button type="button" className="relative group/button" {...props}>
🧹 Nitpick comments (4)
apps/www/components/button.tsx (1)
11-12
: Consider using ButtonHTMLAttributes type.Instead of manually defining keyboard event handlers, extend from React.ButtonHTMLAttributes for better type safety.
-type Props = { +type Props = React.ButtonHTMLAttributes<HTMLButtonElement> & { className?: string; IconLeft?: LucideIcon; label: string; IconRight?: LucideIcon; - onClick?: React.MouseEventHandler<HTMLButtonElement>; - onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>; - onKeyUp?: React.KeyboardEventHandler<HTMLButtonElement>; shiny?: boolean; };apps/www/app/layout.tsx (1)
76-77
: Consider lazy loading consent components.The CookieBanner and ConsentCustomizationDialog could be lazy loaded to improve initial page load performance.
-import { ConsentCustomizationDialog } from "@/components/consent/consent-customization-dialog"; -import CookieBanner from "@/components/consent/cookie-banner"; +const CookieBanner = dynamic(() => import("@/components/consent/cookie-banner"), { + ssr: false, +}); +const ConsentCustomizationDialog = dynamic( + () => import("@/components/consent/consent-customization-dialog"), + { ssr: false } +);apps/www/components/consent/consent-customization-dialog.tsx (1)
20-38
: Optimize animation performance.Move animation variants outside the component to prevent recreation on each render.
+const ANIMATION_VARIANTS = { + dialog: { + hidden: { opacity: 0 }, + visible: { opacity: 1 }, + exit: { opacity: 0 }, + }, + content: { + hidden: { opacity: 0, scale: 0.95 }, + visible: { + opacity: 1, + scale: 1, + transition: { type: "spring", stiffness: 300, damping: 30 }, + }, + exit: { + opacity: 0, + scale: 0.95, + transition: { duration: 0.2 }, + }, + }, +}; -const dialogVariants = { - hidden: { opacity: 0 }, - visible: { opacity: 1 }, - exit: { opacity: 0 }, -}; -const contentVariants = { - hidden: { opacity: 0, scale: 0.95 }, - visible: { - opacity: 1, - scale: 1, - transition: { type: "spring", stiffness: 300, damping: 30 }, - }, - exit: { - opacity: 0, - scale: 0.95, - transition: { duration: 0.2 }, - }, -};apps/www/components/consent/cookie-banner.tsx (1)
147-154
: Improve conditional rendering performance.Move the GDPR check outside the render tree to prevent unnecessary DOM updates.
+ {complianceSettings.gdpr.enabled ? ( <div className="flex flex-col sm:flex-row justify-between gap-2 w-full sm:w-auto"> - {complianceSettings.gdpr.enabled && ( <SecondaryButton onClick={rejectAll} label="Reject All" className="w-full cursor-pointer text-sm sm:w-auto" /> - )} <SecondaryButton onClick={handleCustomize} label="Customize Consent" className="w-full cursor-pointer text-sm sm:w-auto" /> </div> + ) : ( + <SecondaryButton + onClick={handleCustomize} + label="Customize Consent" + className="w-full cursor-pointer text-sm sm:w-auto" + /> + )}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yaml
is excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (10)
apps/www/app/layout.tsx
(2 hunks)apps/www/components/button.tsx
(3 hunks)apps/www/components/consent/consent-customization-dialog.tsx
(1 hunks)apps/www/components/consent/consent-customization-widget.tsx
(1 hunks)apps/www/components/consent/cookie-banner.tsx
(1 hunks)apps/www/components/consent/overlay.tsx
(1 hunks)apps/www/components/footer/footer.tsx
(2 hunks)apps/www/components/ui/switch.tsx
(1 hunks)apps/www/package.json
(2 hunks)apps/www/providers/posthog/PostHogPageView.tsx
(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Test Agent Local / test_agent_local
🔇 Additional comments (11)
apps/www/components/ui/switch.tsx (2)
8-27
: LGTM! Well-implemented accessible switch component.The implementation includes:
- Proper keyboard navigation support
- Focus management
- Transition animations
- ARIA states through Radix UI
14-14
: Verify color scheme alignment with design system.The switch uses custom colors (
bg-[#3CEEAE]/70
for checked state) that might not be part of the design system. Consider using design tokens for consistency.#!/bin/bash # Search for other occurrences of this color in the codebase rg -i '#3CEEAE' --type css --type tsxapps/www/package.json (3)
28-28
: LGTM! Radix UI switch dependency.The
@radix-ui/react-switch
is a stable, well-maintained package that provides accessible switch components.
15-15
: Verify stability of beta dependency.The
@koroflow/[email protected]
package is in beta. Before proceeding:
- Confirm if this package is actively maintained
- Verify if there are any known issues or limitations
- Consider waiting for a stable release
#!/bin/bash # Check package details and recent activity curl -s "https://registry.npmjs.org/@koroflow%2Fcore-react" | jq '{ "latest_version": .["dist-tags"].latest, "modified": .time.modified, "maintainers": .maintainers[].name }' # Check for existing issues in the codebase rg -l '@koroflow/core-react'
Line range hint
1-1
: Architectural Considerations for Cookie Banner Implementation
- The PR introduces UI components but the cookie consent management logic isn't visible in the provided files. Please ensure:
- Proper persistence of user consent
- Integration with your analytics and tracking systems
- Compliance with privacy regulations (GDPR, CCPA)
- Consider documenting the purpose of
@koroflow/core-react
and its role in consent management.#!/bin/bash # Check for consent management implementation echo "Searching for consent management implementation..." rg -l 'consent|gdpr|ccpa|cookie' --type tsx --type ts # Check for analytics integration echo "Checking analytics integration..." rg -l 'analytics|tracking|posthog' --type tsx --type tsapps/www/components/consent/overlay.tsx (1)
13-13
: Verify z-index coordination with other components.The z-index of 40 needs to be verified against other modal/overlay components to ensure proper stacking order.
#!/bin/bash # Search for z-index values across the codebase rg -n "z-\d+" --type css --type tsxapps/www/providers/posthog/PostHogPageView.tsx (1)
11-11
: Verify PostHog initialization timing.The consent check might run before PostHog is fully initialized. Consider adding a ready check.
#!/bin/bash # Search for PostHog initialization code rg -A 5 "posthog\.(init|setup)" --type tsxapps/www/app/layout.tsx (1)
55-55
: Verify consent persistence mechanism.Ensure that consent preferences are properly persisted across sessions.
#!/bin/bash # Search for storage-related code in consent management rg -n "localStorage|sessionStorage" --type tsx rg -n "setCookie|cookie" --type tsxapps/www/components/consent/consent-customization-widget.tsx (2)
75-80
: 🛠️ Refactor suggestionEnhance keyboard accessibility.
The close button should have a more descriptive aria-label and keyboard focus styles.
<Button variant="ghost" size="icon" - className="absolute right-2 top-2" + className="absolute right-2 top-2 focus:ring-2 focus:ring-white/20 focus:outline-none" onClick={onCloseWrapper} - aria-label="Close privacy settings" + aria-label="Close privacy settings dialog"Likely invalid or redundant comment.
51-51
:⚠️ Potential issueRemove debug console.log statement.
Debug statements should not be present in production code.
- console.log("Card rendered with props:", { showCloseButton, onClose, handleSave }); // Debug render
Likely invalid or redundant comment.
apps/www/components/consent/cookie-banner.tsx (1)
58-67
:⚠️ Potential issueFix potential memory leak in useEffect.
The bannerShownRef is not reset in the cleanup function, which could cause issues if the component is unmounted and remounted.
React.useEffect(() => { if (!isMounted) { return; } if (showPopup && !bannerShownRef.current && !hasConsented()) { callbacks.onBannerShown?.(); bannerShownRef.current = true; } + return () => { + bannerShownRef.current = false; + }; }, [showPopup, callbacks, hasConsented, isMounted]);Likely invalid or redundant comment.
<motion.div | ||
className="fixed inset-0 bg-black/50 z-40" | ||
initial={{ opacity: 0 }} | ||
animate={{ opacity: 1 }} | ||
exit={{ opacity: 0 }} | ||
/> |
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.
🛠️ Refactor suggestion
Add ARIA attributes for accessibility.
The overlay should have appropriate ARIA attributes to improve accessibility for screen readers.
<motion.div
className="fixed inset-0 bg-black/50 z-40"
+ role="presentation"
+ aria-hidden="true"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<motion.div | |
className="fixed inset-0 bg-black/50 z-40" | |
initial={{ opacity: 0 }} | |
animate={{ opacity: 1 }} | |
exit={{ opacity: 0 }} | |
/> | |
<motion.div | |
className="fixed inset-0 bg-black/50 z-40" | |
role="presentation" | |
aria-hidden="true" | |
initial={{ opacity: 0 }} | |
animate={{ opacity: 1 }} | |
exit={{ opacity: 0 }} | |
/> |
}, [consents, setConsent, onSave, saveConsents]); | ||
|
||
const rejectAll = React.useCallback(() => { | ||
saveConsents("necessary"); |
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.
Remove debug console.log statements.
Debug statements should not be present in production code.
- console.log("onSaveWrapper called");
- console.log("onCloseWrapper called");
Also applies to: 59-59
|
||
const handleConsentChange = React.useCallback( | ||
(name: string, checked: boolean) => { | ||
setConsent(name as any, checked); |
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.
🛠️ Refactor suggestion
Remove type assertion using any
.
Using any
type defeats TypeScript's type checking. Consider getting the correct type from the consent manager.
- setConsent(name as any, checked);
+ setConsent(name as keyof typeof consents, checked);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
setConsent(name as any, checked); | |
setConsent(name as keyof typeof consents, checked); |
<button | ||
type="button" | ||
onClick={link.onClick} | ||
className="text-sm font-normal transition hover:text-white/40 text-white/70" | ||
> | ||
{link.title} | ||
</button> |
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.
🛠️ Refactor suggestion
Enhance button accessibility.
The button element needs proper ARIA attributes and keyboard focus styles.
<button
type="button"
onClick={link.onClick}
- className="text-sm font-normal transition hover:text-white/40 text-white/70"
+ className="text-sm font-normal transition hover:text-white/40 text-white/70 focus:ring-2 focus:ring-white/20 focus:outline-none"
+ aria-label={`${link.title} settings`}
>
{link.title}
</button>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<button | |
type="button" | |
onClick={link.onClick} | |
className="text-sm font-normal transition hover:text-white/40 text-white/70" | |
> | |
{link.title} | |
</button> | |
<button | |
type="button" | |
onClick={link.onClick} | |
className="text-sm font-normal transition hover:text-white/40 text-white/70 focus:ring-2 focus:ring-white/20 focus:outline-none" | |
aria-label={`${link.title} settings`} | |
> | |
{link.title} | |
</button> |
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.
Actionable comments posted: 5
🧹 Nitpick comments (4)
apps/www/components/consent/cookie-banner.tsx (3)
3-13
: Consider reorganizing imports for better maintainability.Group imports into these categories:
- External dependencies
- Internal UI components
- Utils and hooks
- import { useConsentManager } from "@koroflow/core-react"; - import { AnimatePresence, motion } from "framer-motion"; - import { X } from "lucide-react"; - import * as React from "react"; - import { createPortal } from "react-dom"; - - import { Button } from "@/components/ui/button"; - import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; - import { cn } from "@/lib/utils"; - import { PrimaryButton, SecondaryButton } from "../button"; - import { usePostHog } from "posthog-js/react"; + import * as React from "react"; + import { createPortal } from "react-dom"; + import { useConsentManager } from "@koroflow/core-react"; + import { AnimatePresence, motion } from "framer-motion"; + import { X } from "lucide-react"; + import { usePostHog } from "posthog-js/react"; + + import { Button } from "@/components/ui/button"; + import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; + import { PrimaryButton, SecondaryButton } from "../button"; + + import { cn } from "@/lib/utils";
18-24
: Add JSDoc documentation for the interface.Adding documentation will improve code maintainability and help other developers understand the purpose of each prop.
+ /** + * Props for the CookieBanner component + * @interface PrivacyPopupProps + * @extends {React.HTMLAttributes<HTMLDivElement>} + */ interface PrivacyPopupProps extends React.HTMLAttributes<HTMLDivElement> { + /** Description text for the cookie banner */ bannerDescription?: string; + /** Title text for the cookie banner */ bannerTitle?: string; + /** Horizontal positioning of the banner */ horizontalPosition?: HorizontalPosition; + /** Vertical positioning of the banner */ verticalPosition?: VerticalPosition; + /** Whether to show the close button */ showCloseButton?: boolean; }
52-58
: Add cleanup for bannerShownRef to prevent memory leaks.The
bannerShownRef
should be reset when the component unmounts.React.useEffect(() => { setIsMounted(true); - return () => setIsMounted(false); + return () => { + setIsMounted(false); + bannerShownRef.current = false; + }; }, []);apps/www/components/consent/consent-customization-widget.tsx (1)
92-93
: Simplify keyboard event handlingHaving both
onKeyUp
andonKeyDown
handlers for the same key can lead to duplicate actions. Consider using only one, preferablyonKeyDown
, for a consistent and immediate response to key presses.Apply this diff to remove the redundant handler:
onClick={() => toggleAccordion(consent.name)} - onKeyUp={(e) => e.key === "Enter" && toggleAccordion(consent.name)} onKeyDown={(e) => e.key === "Enter" && toggleAccordion(consent.name)}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/www/components/consent/consent-customization-dialog.tsx
(1 hunks)apps/www/components/consent/consent-customization-widget.tsx
(1 hunks)apps/www/components/consent/cookie-banner.tsx
(1 hunks)apps/www/components/consent/overlay.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/www/components/consent/overlay.tsx
🔇 Additional comments (1)
apps/www/components/consent/cookie-banner.tsx (1)
26-49
: 🛠️ Refactor suggestionEnhance error handling and accessibility.
Consider these improvements:
- Wrap the component in an error boundary
- Add ARIA live region for screen readers
- Add keyboard navigation support
Let me check if error boundaries are implemented elsewhere:
const BannerContent = () => ( | ||
<AnimatePresence> | ||
{showPopup && !isPrivacyDialogOpen && ( | ||
<motion.dialog | ||
className="fixed inset-0 z-50 flex items-end sm:items-center justify-center px-4 sm:px-0" | ||
initial={{ opacity: 0 }} | ||
animate={{ opacity: 1 }} | ||
exit={{ opacity: 0 }} | ||
aria-modal="true" | ||
aria-labelledby="cookie-consent-title" | ||
> | ||
<motion.div | ||
className={positionClasses} | ||
initial={{ opacity: 0, y: 50 }} | ||
animate={{ opacity: 1, y: 0 }} | ||
exit={{ opacity: 0, y: 50 }} | ||
transition={{ type: "spring", stiffness: 300, damping: 30 }} | ||
ref={ref} | ||
> | ||
<Card className="w-full relative border bg-gradient-to-b from-[#111111] to-black border-t-[.75px] border-white/20 overflow-hidden"> | ||
<CardHeader className="space-y-2 pb-0 border-white/10 editor-top-gradient"> | ||
{showCloseButton && ( | ||
<Button | ||
variant="ghost" | ||
size="icon" | ||
className="absolute right-2 top-2" | ||
onClick={handleClose} | ||
aria-label="Close cookie consent banner" | ||
> | ||
<X className="h-4 w-4" /> | ||
</Button> | ||
)} | ||
<CardTitle id="cookie-consent-title" className="text-lg sm:text-xl"> | ||
{bannerTitle} | ||
</CardTitle> | ||
<CardDescription className="text-sm sm:text-base"> | ||
{bannerDescription} | ||
</CardDescription> | ||
</CardHeader> | ||
<CardFooter className="flex flex-col sm:flex-row justify-between gap-4 p-4 sm:p-6"> | ||
<div className="flex flex-col sm:flex-row justify-between gap-2 w-full sm:w-auto"> | ||
{complianceSettings.gdpr.enabled && ( | ||
<SecondaryButton | ||
onClick={rejectAll} | ||
label="Reject All" | ||
className="w-full cursor-pointer text-sm sm:w-auto" | ||
/> | ||
)} | ||
<SecondaryButton | ||
onClick={handleCustomize} | ||
label="Customize Consent" | ||
className="w-full cursor-pointer text-sm sm:w-auto" | ||
/> | ||
</div> | ||
<PrimaryButton | ||
onClick={acceptAll} | ||
label="Accept All" | ||
className="w-full text-sm sm:w-auto cursor-pointer" | ||
/> | ||
</CardFooter> | ||
</Card> | ||
</motion.div> | ||
</motion.dialog> | ||
)} | ||
</AnimatePresence> | ||
); |
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.
🛠️ Refactor suggestion
Enhance modal accessibility and keyboard interaction.
The cookie banner modal needs accessibility improvements:
- Add focus trap for keyboard navigation
- Add escape key handler
- Ensure animations respect reduced motion preferences
+ import { useCallback, useEffect } from 'react';
+ import { usePreferReducedMotion } from 'framer-motion';
const BannerContent = () => {
+ const prefersReducedMotion = usePreferReducedMotion();
+
+ const handleEscape = useCallback((event: KeyboardEvent) => {
+ if (event.key === 'Escape' && showCloseButton) {
+ handleClose();
+ }
+ }, [handleClose, showCloseButton]);
+
+ useEffect(() => {
+ document.addEventListener('keydown', handleEscape);
+ return () => document.removeEventListener('keydown', handleEscape);
+ }, [handleEscape]);
return (
<AnimatePresence>
{showPopup && !isPrivacyDialogOpen && (
<motion.dialog
className="fixed inset-0 z-50 flex items-end sm:items-center justify-center px-4 sm:px-0"
- initial={{ opacity: 0 }}
- animate={{ opacity: 1 }}
- exit={{ opacity: 0 }}
+ initial={prefersReducedMotion ? {} : { opacity: 0 }}
+ animate={prefersReducedMotion ? {} : { opacity: 1 }}
+ exit={prefersReducedMotion ? {} : { opacity: 0 }}
aria-modal="true"
aria-labelledby="cookie-consent-title"
+ role="dialog"
>
Consider using the focus-trap-react
library for managing focus within the modal:
import FocusTrap from 'focus-trap-react';
// Wrap the Card component with FocusTrap
<FocusTrap>
<Card className="w-full relative...">
{/* existing content */}
</Card>
</FocusTrap>
const acceptAll = React.useCallback(() => { | ||
const allConsents = Object.keys(consents) as (keyof typeof consents)[]; | ||
for (const consentName of allConsents) { | ||
setConsent(consentName, true); | ||
} | ||
saveConsents("all"); | ||
posthog.capture("consent", { | ||
consent: "necessary", | ||
}); | ||
}, [consents, setConsent, saveConsents, posthog]); | ||
|
||
const rejectAll = React.useCallback(() => { | ||
saveConsents("necessary"); | ||
posthog.capture("consent", { | ||
consent: "necessary", | ||
}); | ||
}, [saveConsents, posthog]); |
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.
🛠️ Refactor suggestion
Fix duplicate PostHog event payload and add error handling.
The PostHog event payload is duplicated between acceptAll
and rejectAll
. Also, consider adding error handling for PostHog events.
+ const captureConsent = (type: 'all' | 'necessary') => {
+ try {
+ posthog.capture("consent", { consent: "necessary" });
+ } catch (error) {
+ console.error('Failed to capture consent event:', error);
+ }
+ };
const acceptAll = React.useCallback(() => {
const allConsents = Object.keys(consents) as (keyof typeof consents)[];
for (const consentName of allConsents) {
setConsent(consentName, true);
}
saveConsents("all");
- posthog.capture("consent", {
- consent: "necessary",
- });
+ captureConsent('all');
}, [consents, setConsent, saveConsents, posthog]);
const rejectAll = React.useCallback(() => {
saveConsents("necessary");
- posthog.capture("consent", {
- consent: "necessary",
- });
+ captureConsent('necessary');
}, [saveConsents, posthog]);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const acceptAll = React.useCallback(() => { | |
const allConsents = Object.keys(consents) as (keyof typeof consents)[]; | |
for (const consentName of allConsents) { | |
setConsent(consentName, true); | |
} | |
saveConsents("all"); | |
posthog.capture("consent", { | |
consent: "necessary", | |
}); | |
}, [consents, setConsent, saveConsents, posthog]); | |
const rejectAll = React.useCallback(() => { | |
saveConsents("necessary"); | |
posthog.capture("consent", { | |
consent: "necessary", | |
}); | |
}, [saveConsents, posthog]); | |
const captureConsent = (type: 'all' | 'necessary') => { | |
try { | |
posthog.capture("consent", { consent: "necessary" }); | |
} catch (error) { | |
console.error('Failed to capture consent event:', error); | |
} | |
}; | |
const acceptAll = React.useCallback(() => { | |
const allConsents = Object.keys(consents) as (keyof typeof consents)[]; | |
for (const consentName of allConsents) { | |
setConsent(consentName, true); | |
} | |
saveConsents("all"); | |
captureConsent('all'); | |
}, [consents, setConsent, saveConsents, posthog]); | |
const rejectAll = React.useCallback(() => { | |
saveConsents("necessary"); | |
captureConsent('necessary'); | |
}, [saveConsents, posthog]); |
const handleClose = React.useCallback(() => { | ||
setShowPopup(false); | ||
callbacks.onBannerClosed?.(); | ||
}, [setShowPopup, callbacks]); | ||
|
||
const handleCustomize = React.useCallback(() => { | ||
setIsPrivacyDialogOpen(true); | ||
}, [setIsPrivacyDialogOpen]); | ||
|
||
const positionClasses = cn( | ||
"fixed z-50 max-w-md", | ||
{ | ||
"left-4": horizontalPosition === "left", | ||
"right-4": horizontalPosition === "right", | ||
"left-1/2 -translate-x-1/2": horizontalPosition === "center", | ||
"top-4": verticalPosition === "top", | ||
"bottom-4": verticalPosition === "bottom", | ||
}, | ||
className, | ||
); | ||
|
||
// Early return for SSR and when user has consented | ||
if (!isMounted || (hasConsented() && !showPopup)) { | ||
return null; | ||
} | ||
|
||
const BannerContent = () => ( | ||
<AnimatePresence> | ||
{showPopup && !isPrivacyDialogOpen && ( | ||
<motion.dialog | ||
className="fixed inset-0 z-50 flex items-end sm:items-center justify-center px-4 sm:px-0" | ||
initial={{ opacity: 0 }} | ||
animate={{ opacity: 1 }} | ||
exit={{ opacity: 0 }} | ||
aria-modal="true" | ||
aria-labelledby="cookie-consent-title" | ||
> | ||
<motion.div | ||
className={positionClasses} | ||
initial={{ opacity: 0, y: 50 }} | ||
animate={{ opacity: 1, y: 0 }} | ||
exit={{ opacity: 0, y: 50 }} | ||
transition={{ type: "spring", stiffness: 300, damping: 30 }} | ||
ref={ref} | ||
> | ||
<Card className="w-full relative border bg-gradient-to-b from-[#111111] to-black border-t-[.75px] border-white/20 overflow-hidden"> | ||
<CardHeader className="space-y-2 pb-0 border-white/10 editor-top-gradient"> | ||
{showCloseButton && ( | ||
<Button | ||
variant="ghost" | ||
size="icon" | ||
className="absolute right-2 top-2" | ||
onClick={handleClose} | ||
aria-label="Close cookie consent banner" | ||
> | ||
<X className="h-4 w-4" /> | ||
</Button> | ||
)} | ||
<CardTitle id="cookie-consent-title" className="text-lg sm:text-xl"> | ||
{bannerTitle} | ||
</CardTitle> | ||
<CardDescription className="text-sm sm:text-base"> | ||
{bannerDescription} | ||
</CardDescription> | ||
</CardHeader> | ||
<CardFooter className="flex flex-col sm:flex-row justify-between gap-4 p-4 sm:p-6"> | ||
<div className="flex flex-col sm:flex-row justify-between gap-2 w-full sm:w-auto"> | ||
{complianceSettings.gdpr.enabled && ( | ||
<SecondaryButton | ||
onClick={rejectAll} | ||
label="Reject All" | ||
className="w-full cursor-pointer text-sm sm:w-auto" | ||
/> | ||
)} | ||
<SecondaryButton | ||
onClick={handleCustomize} | ||
label="Customize Consent" | ||
className="w-full cursor-pointer text-sm sm:w-auto" | ||
/> | ||
</div> | ||
<PrimaryButton | ||
onClick={acceptAll} | ||
label="Accept All" | ||
className="w-full text-sm sm:w-auto cursor-pointer" | ||
/> | ||
</CardFooter> | ||
</Card> | ||
</motion.div> | ||
</motion.dialog> | ||
)} | ||
</AnimatePresence> | ||
); | ||
|
||
return isMounted && createPortal(<BannerContent />, document.body); | ||
}, | ||
); | ||
|
||
CookieBanner.displayName = "CookieBanner"; | ||
|
||
export default CookieBanner; |
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.
💡 Codebase verification
Add test coverage for the CookieBanner component
The CookieBanner component lacks test coverage despite handling critical user consent functionality. Given that the project uses Vitest for testing, implement tests for:
- Consent management logic (accept, reject, customize flows)
- PostHog event tracking
- UI interactions and accessibility features
- Animation states and portal rendering
🔗 Analysis chain
Add comprehensive test coverage.
The component lacks test coverage for:
- Consent management logic
- UI interactions
- Accessibility features
- Animation behaviors
- PostHog event tracking
Let me check for existing test files:
Would you like me to help create a test suite for this component?
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for test files related to the cookie banner
rg -l "cookie-banner\.(test|spec)\.(tsx|ts)" --type typescript
Length of output: 99
Script:
#!/bin/bash
# Search for any test files related to cookie banner
echo "=== Searching for test files ==="
fd "cookie-banner.*\.(test|spec)\.(tsx|ts|js|jsx)" || true
echo -e "\n=== Searching for CookieBanner in test files ==="
rg "CookieBanner.*test" || true
echo -e "\n=== Checking for test setup files ==="
fd "jest.config|vitest.config" || true
Length of output: 727
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/www/components/consent/consent-customization-widget.tsx (1)
89-96
: 🛠️ Refactor suggestionConvert div to button element for better accessibility.
Using a
div
withrole="button"
is less semantic than using a nativebutton
element.Apply this diff:
- <div - className="flex-grow" - role="button" - tabIndex={0} - onClick={() => toggleAccordion(consent.name)} - onKeyUp={(e) => e.key === "Enter" && toggleAccordion(consent.name)} - onKeyDown={(e) => e.key === "Enter" && toggleAccordion(consent.name)} - > + <button + type="button" + className="flex-grow text-left" + onClick={() => toggleAccordion(consent.name)} + >🧰 Tools
🪛 Biome (1.9.4)
[error] 90-91: The elements with the following roles can be changed to the following elements:
For examples and more information, see WAI-ARIA Roles
(lint/a11y/useSemanticElements)
🧹 Nitpick comments (2)
apps/www/components/consent/consent-customization-widget.tsx (2)
31-41
: Add error handling for analytics tracking.The PostHog analytics tracking calls could fail silently. Consider adding try-catch blocks to handle potential failures gracefully.
Example implementation:
const handleSaveConsents = React.useCallback(() => { saveConsents("custom"); - posthog.capture("consent", { - consent: "custom", - consents, - }); + try { + posthog.capture("consent", { + consent: "custom", + consents, + }); + } catch (error) { + console.error("Failed to track consent analytics:", error); + } if (onSave) { onSave(); } }, [saveConsents, posthog, consents, onSave]);Apply similar error handling to other analytics tracking calls in
acceptAll
andrejectAll
functions.Also applies to: 50-62, 64-72
106-111
: Enhance switch component accessibility.The switch component could benefit from additional accessibility improvements.
Add ARIA labels and descriptions:
<Switch checked={consents[consent.name]} onCheckedChange={(checked) => handleConsentChange(consent.name, checked)} disabled={consent.disabled} - className="ml-4" + className="ml-4" + aria-label={`Toggle ${consent.name.replace("_", " ")}`} + aria-describedby={`${consent.name}-description`} />Also, consider adding an invisible span with ID matching the aria-describedby for screen readers:
<AccordionContent> - <p className="text-sm text-muted-foreground pb-4">{consent.description}</p> + <p className="text-sm text-muted-foreground pb-4" id={`${consent.name}-description`}> + {consent.description} + </p> </AccordionContent>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/www/components/consent/consent-customization-widget.tsx
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
apps/www/components/consent/consent-customization-widget.tsx
[error] 90-91: The elements with the following roles can be changed to the following elements:
For examples and more information, see WAI-ARIA Roles
(lint/a11y/useSemanticElements)
🔇 Additional comments (2)
apps/www/components/consent/consent-customization-widget.tsx (2)
1-14
: LGTM! Clean imports and well-typed props interface.The imports are well-organized, and the props interface is properly typed with TypeScript.
16-24
: LGTM! Well-structured component initialization.Good use of
forwardRef
and proper hook initialization. The state management is clean and follows React best practices.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
What does this PR do?
Fixes # (issue)
If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists
Type of change
How should this be tested?
Checklist
Required
pnpm build
pnpm fmt
console.logs
git pull origin main
Appreciated
Summary by CodeRabbit
New Features
CookieBanner
,ConsentCustomizationDialog
,ConsentCustomizationWidget
,Overlay
, andSwitch
.Improvements
Technical Updates