Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions frontend/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { getCurrentUser } from '@/lib/auth';
import { MainSwitcher } from '@/components/header/MainSwitcher';
import { AppChrome } from '@/components/header/AppChrome';

import { CookieBanner } from '@/components/shared/CookieBanner';

export const dynamic = 'force-dynamic';

export default async function LocaleLayout({
Expand Down Expand Up @@ -45,6 +47,7 @@ export default async function LocaleLayout({

<Footer />
<Toaster position="top-right" richColors expand />
<CookieBanner />
</ThemeProvider>
</NextIntlClientProvider>
);
Expand Down
91 changes: 91 additions & 0 deletions frontend/components/shared/CookieBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use client';

import { useState, useEffect } from 'react';
import { X } from 'lucide-react';
import { Link } from '@/i18n/routing';
import { Button } from '@/components/ui/button';
import { useTranslations } from 'next-intl';

export function CookieBanner() {
const t = useTranslations('CookieBanner');
const [isVisible, setIsVisible] = useState(false);
const [isMounted, setIsMounted] = useState(false);

useEffect(() => {
setIsMounted(true);
const consent = localStorage.getItem('cookie-consent');
if (!consent) {
const timer = setTimeout(() => setIsVisible(true), 500);
return () => clearTimeout(timer);
}
}, []);

const handleAccept = () => {
try {
localStorage.setItem('cookie-consent', 'accepted');
} catch (error) {
console.error('Failed to save cookie consent:', error);
}
setIsVisible(false);
};

const handleDecline = () => {
try {
localStorage.setItem('cookie-consent', 'declined');
} catch (error) {
console.error('Failed to save cookie consent:', error);
}
setIsVisible(false);
};
Comment on lines +23 to +39
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for cookie consent checks and tracking script integration
rg -n -C3 "cookie-consent" --type=ts --type=tsx --type=js --type=jsx

Repository: DevLoversTeam/devlovers.net

Length of output: 98


🏁 Script executed:

#!/bin/bash
rg -n -C3 "(gtag|analytics|tracking)" --type=ts --type=tsx --type=js --type=jsx -g '!node_modules'

Repository: DevLoversTeam/devlovers.net

Length of output: 98


🏁 Script executed:

#!/bin/bash
# Also search for common patterns that might indicate consent enforcement
rg -n "cookie|consent|GDPR|tracking" frontend/components/shared/CookieBanner.tsx -C5

Repository: DevLoversTeam/devlovers.net

Length of output: 776


🏁 Script executed:

#!/bin/bash
# Check if there's an env file or config that might load tracking conditionally
fd -e "env" -e "config" -e "next.config" -e "vercel.json" . 2>/dev/null | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Search for cookie-consent usage without file type flags
rg -n -C3 "cookie-consent"

Repository: DevLoversTeam/devlovers.net

Length of output: 1442


🏁 Script executed:

#!/bin/bash
# Search for tracking and analytics patterns
rg -n "gtag|analytics|tracking|pixel" --max-count=20

Repository: DevLoversTeam/devlovers.net

Length of output: 20569


🏁 Script executed:

#!/bin/bash
# Look for conditional script loading or consent checks
rg -n "document\.head|Script|tracking|gtag" . | head -40

Repository: DevLoversTeam/devlovers.net

Length of output: 6455


🏁 Script executed:

#!/bin/bash
# Check for Next.js Script component usage
rg -n "next/script|<Script" . | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Look at the full CookieBanner component structure
cat -n frontend/components/shared/CookieBanner.tsx

Repository: DevLoversTeam/devlovers.net

Length of output: 3506


🏁 Script executed:

#!/bin/bash
# Search for @vercel/analytics usage
rg -n "@vercel/analytics|Analytics" . --max-count=30

Repository: DevLoversTeam/devlovers.net

Length of output: 399


🏁 Script executed:

#!/bin/bash
# Check the main app layout/root files
find frontend/app -name "layout.*" -o -name "page.*" | head -10 | xargs -I {} sh -c 'echo "=== {} ===" && head -50 {}'

Repository: DevLoversTeam/devlovers.net

Length of output: 8649


🏁 Script executed:

#!/bin/bash
# Look for any script initialization in root app files
rg -n "import.*Analytics|useEffect.*tracking|gtag" frontend/app --max-count=20

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Check if there's any conditional loading of scripts based on consent
rg -n "cookie-consent.*Analytics|Analytics.*cookie|consent.*gtag" . --max-count=20

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Find and check the root layout file
find frontend/app -maxdepth 2 -name "layout.tsx" | head -3 | xargs -I {} sh -c 'echo "=== {} ===" && cat -n {}'

Repository: DevLoversTeam/devlovers.net

Length of output: 3440


🏁 Script executed:

#!/bin/bash
# Check for any configuration that might initialize analytics
rg -n "instrument|initialize|setup" frontend/app --type=ts --type=tsx 2>/dev/null | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Double-check that @vercel/analytics is truly not imported anywhere
rg -r "from.*@vercel/analytics|import.*@vercel/analytics" frontend/

Repository: DevLoversTeam/devlovers.net

Length of output: 326


Remove or repurpose the CookieBanner—currently it stores consent with no tracking system to enforce it against.

The component saves user consent preferences to localStorage, but the codebase has no analytics or tracking systems integrated. The @vercel/analytics package is installed but unused. Users are prompted to accept/decline cookies, but this preference has no effect since there are no cookies or trackers to control.

Either remove the banner if no analytics are planned, or implement a complete consent-driven tracking solution where:

  1. Analytics/tracking scripts only load after user accepts consent
  2. The stored preference is actively checked before initializing any tracking
  3. Users can revoke consent and stop tracking

As-is, the banner creates a compliance-adjacent appearance without actual protection, which is worse than no banner.

🤖 Prompt for AI Agents
In @frontend/components/shared/CookieBanner.tsx around lines 23 - 31, The
CookieBanner currently writes consent to localStorage via handleAccept and
handleDecline but no tracking reacts to that; either remove the CookieBanner
component entirely (and delete handleAccept/handleDecline and any imports) or
implement consent-driven analytics: add a single initializer function (e.g.,
initializeAnalytics or startAnalytics) that imports/initializes
@vercel/analytics and only call it when handleAccept runs, ensure app startup
checks localStorage['cookie-consent'] before calling initializeAnalytics, and
implement a revokeConsent function that clears stored consent and calls a
stopAnalytics/cleanup routine to disable tracking; update CookieBanner to
reflect these flows and ensure no analytics module is loaded/initialized until
consent === 'accepted'.


if (!isMounted || !isVisible) return null;

return (
<div className="fixed bottom-0 left-0 right-0 z-[100] p-4 md:p-6 animate-in slide-in-from-bottom-full fade-in duration-700">
<div className="mx-auto max-w-4xl rounded-2xl border border-gray-200 bg-white/90 p-5 shadow-2xl backdrop-blur-md dark:border-gray-800 dark:bg-gray-900/90 md:flex md:items-center md:justify-between md:gap-6">
<div className="mb-4 md:mb-0 md:flex-1">
<h3 className="mb-1 text-lg font-semibold text-gray-900 dark:text-white">
{t('title')}
</h3>
<p className="text-sm leading-relaxed text-gray-600 dark:text-gray-300">
{t('description')}{' '}
<Link
href="/privacy-policy"
className="font-medium text-blue-600 underline decoration-blue-600/30 underline-offset-4 transition-colors hover:text-blue-700 hover:decoration-blue-700 dark:text-blue-500 dark:hover:text-blue-400"
>
{t('policyLink')}
</Link>
.
</p>
</div>

<div className="flex flex-col gap-3 sm:flex-row sm:items-center">
<Button
variant="outline"
size="sm"
onClick={handleDecline}
className="w-full sm:w-auto"
>
{t('decline')}
</Button>
<Button
variant="primary"
size="sm"
onClick={handleAccept}
className="w-full sm:w-auto shadow-lg shadow-blue-500/20"
>
{t('accept')}
</Button>
</div>

<button
onClick={handleDecline}
className="absolute right-2 top-2 rounded-full p-1 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300 md:hidden"
aria-label={t('decline')}
>
<X size={20} />
</button>
</div>
</div>
);
}
Comment on lines +9 to +91
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing: Consent revocation mechanism.

Once a user makes a choice, there's no way for them to change their mind or revoke consent. GDPR requires that users can withdraw consent as easily as they can give it. Consider adding a mechanism (e.g., a footer link or settings page) where users can clear their consent and see the banner again.

Would you like me to suggest an implementation for a consent revocation mechanism, such as a "Cookie Settings" link in the footer that clears the localStorage value?

7 changes: 7 additions & 0 deletions frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,12 @@
"5": "I recommend DevLovers to all my mentees. It's become essential for anyone preparing for tech interviews."
}
}
},
"CookieBanner": {
"title": "🍪 We value your privacy",
"description": "We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking \"Accept\", you consent to our use of cookies. Read our",
"policyLink": "Privacy Policy",
"accept": "Accept Cookies",
"decline": "Decline"
}
}
7 changes: 7 additions & 0 deletions frontend/messages/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,12 @@
"5": "Polecam DevLovers wszystkim moim podopiecznym. Stało się to niezbędne dla każdego, kto przygotowuje się do technicznych rozmów kwalifikacyjnych."
}
}
},
"CookieBanner": {
"title": "🍪 Dbamy o Twoją prywatność",
"description": "Używamy plików cookie, aby poprawić jakość przeglądania, wyświetlać spersonalizowane treści i analizować nasz ruch. Klikając „Zaakceptuj”, wyrażasz zgodę na używanie plików cookie. Przeczytaj naszą",
"policyLink": "Politykę Prywatności",
"accept": "Zaakceptuj",
"decline": "Odrzuć"
}
}
7 changes: 7 additions & 0 deletions frontend/messages/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,12 @@
"5": "Я рекомендую DevLovers всім своїм підопічним. Це стало важливим для всіх, хто готується до технічних співбесід."
}
}
},
"CookieBanner": {
"title": "🍪 Ми цінуємо вашу приватність",
"description": "Ми використовуємо файли cookie для покращення вашого досвіду, персоналізації контенту та аналізу трафіку. Натискаючи \"Прийняти\", ви погоджуєтесь на використання cookie. Читайте нашу",
"policyLink": "Політику конфіденційності",
"accept": "Прийняти",
"decline": "Відхилити"
}
}