feat(compliance): add GDPR/RODO compliant cookie consent banner
Implemented cookie consent banner to comply with EU regulations (GDPR/RODO). The banner appears on first visit and stores user preference in localStorage. Features: - Non-intrusive bottom banner with clear messaging - Accept/Decline options for user choice - Link to privacy policy in About Us page - Responsive design for mobile and desktop - Auto-dismisses after consent with 1s delay on first show - High z-index to stay above all content Also added comprehensive Privacy & Cookies section to About Us page explaining: - What cookies we use (essential, analytics, preferences) - How we handle user data - GDPR/RODO compliance statements - Contact information for privacy questions Changes: - Created CookieConsent component with modern UI - Integrated banner into App.jsx - Updated about-us.md with privacy policy section
This commit is contained in:
@@ -1,9 +1,33 @@
|
||||
Hi, I’m Radek – a software engineer, a West Coast Swing dancer and the person behind **spotlight.cam**.
|
||||
Hi, I'm Radek – a software engineer, a West Coast Swing dancer and the person behind **spotlight.cam**.
|
||||
|
||||
|
||||
Spotlight.cam is a project built by someone who actually stands in the same registration lines, dances in the same heats and scrolls through the same event pages as you :P
|
||||
|
||||
|
||||
If we ever meet at an event, I’ll probably be somewhere near the dance floor, probably pressing “record” on someone’s spotlight.
|
||||
If we ever meet at an event, I'll probably be somewhere near the dance floor, probably pressing "record" on someone's spotlight.
|
||||
|
||||
To be fair, the original idea for this service was actually dropped on me by a friend who was tired of hunting for someone to film her dances – I just did what any backend developer / DevOps / Linux admin would do: said “okay, that shouldn’t be that hard… right?”, opened my editor, set up a few servers and scripts… and suddenly we had a new project on our hands 😅 I also had a not-so-secret teammate: AI. Without it, this would probably still be stuck in my “one day” folder for about a year 😄
|
||||
To be fair, the original idea for this service was actually dropped on me by a friend who was tired of hunting for someone to film her dances – I just did what any backend developer / DevOps / Linux admin would do: said "okay, that shouldn't be that hard… right?", opened my editor, set up a few servers and scripts… and suddenly we had a new project on our hands 😅 I also had a not-so-secret teammate: AI. Without it, this would probably still be stuck in my "one day" folder for about a year 😄
|
||||
|
||||
---
|
||||
|
||||
## Privacy & Cookies
|
||||
|
||||
We use cookies and similar technologies to provide you with a better experience. Here's what you should know:
|
||||
|
||||
### What cookies do we use?
|
||||
|
||||
- **Essential cookies**: Required for authentication and core functionality (login sessions, security)
|
||||
- **Analytics**: To understand how people use the platform and improve it
|
||||
- **Preferences**: To remember your settings and preferences
|
||||
|
||||
### Your data
|
||||
|
||||
We respect your privacy and comply with GDPR/RODO regulations. We:
|
||||
- Only collect data necessary for the service to function
|
||||
- Never sell your personal information to third parties
|
||||
- Store your data securely
|
||||
- Allow you to delete your account and data at any time
|
||||
|
||||
### Contact
|
||||
|
||||
If you have any questions about privacy or cookies, feel free to reach out through our [contact page](/contact).
|
||||
@@ -26,6 +26,7 @@ import HowItWorksPage from './pages/HowItWorksPage';
|
||||
import NotFoundPage from './pages/NotFoundPage';
|
||||
import VerificationBanner from './components/common/VerificationBanner';
|
||||
import InstallPWA from './components/pwa/InstallPWA';
|
||||
import CookieConsent from './components/common/CookieConsent';
|
||||
|
||||
// Protected Route Component with Verification Banner
|
||||
const ProtectedRoute = ({ children }) => {
|
||||
@@ -77,6 +78,9 @@ function App() {
|
||||
{/* PWA Install Prompt */}
|
||||
<InstallPWA />
|
||||
|
||||
{/* Cookie Consent Banner */}
|
||||
<CookieConsent />
|
||||
|
||||
{/* Toast Notifications */}
|
||||
<Toaster
|
||||
position="top-right"
|
||||
|
||||
85
frontend/src/components/common/CookieConsent.jsx
Normal file
85
frontend/src/components/common/CookieConsent.jsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Cookie, X } from 'lucide-react';
|
||||
|
||||
/**
|
||||
* GDPR/RODO compliant cookie consent banner
|
||||
* Shows at bottom of screen on first visit, stores consent in localStorage
|
||||
*/
|
||||
const CookieConsent = () => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if user has already consented
|
||||
const hasConsented = localStorage.getItem('cookieConsent');
|
||||
if (!hasConsented) {
|
||||
// Show banner after a short delay for better UX
|
||||
setTimeout(() => setIsVisible(true), 1000);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleAccept = () => {
|
||||
localStorage.setItem('cookieConsent', 'accepted');
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
const handleDecline = () => {
|
||||
localStorage.setItem('cookieConsent', 'declined');
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-[9999] p-4 bg-gradient-to-t from-black/10 to-transparent pointer-events-none">
|
||||
<div className="max-w-7xl mx-auto pointer-events-auto">
|
||||
<div className="bg-white rounded-lg shadow-2xl border border-gray-200 p-4 sm:p-6">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
|
||||
{/* Icon */}
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-12 h-12 bg-primary-100 rounded-full flex items-center justify-center">
|
||||
<Cookie className="w-6 h-6 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">
|
||||
We use cookies
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600">
|
||||
We use cookies and similar technologies to enhance your experience, analyze site traffic,
|
||||
and for authentication purposes. By clicking "Accept", you consent to our use of cookies.{' '}
|
||||
<a
|
||||
href="/about-us"
|
||||
className="text-primary-600 hover:text-primary-700 underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center gap-2 flex-shrink-0 w-full sm:w-auto">
|
||||
<button
|
||||
onClick={handleDecline}
|
||||
className="flex-1 sm:flex-none px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||
>
|
||||
Decline
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAccept}
|
||||
className="flex-1 sm:flex-none px-4 py-2 text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 rounded-lg transition-colors"
|
||||
>
|
||||
Accept
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CookieConsent;
|
||||
Reference in New Issue
Block a user