Improve GDPR implementation

This commit is contained in:
Urtzi Alfaro
2025-10-16 07:28:04 +02:00
parent dbb48d8e2c
commit b6cb800758
37 changed files with 4876 additions and 307 deletions

View File

@@ -25,6 +25,8 @@ interface SimpleUserRegistration {
password: string;
confirmPassword: string;
acceptTerms: boolean;
marketingConsent: boolean;
analyticsConsent: boolean;
}
// Define the steps for the registration process
@@ -41,7 +43,9 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
email: '',
password: '',
confirmPassword: '',
acceptTerms: false
acceptTerms: false,
marketingConsent: false,
analyticsConsent: false
});
const [errors, setErrors] = useState<Partial<SimpleUserRegistration>>({});
@@ -135,10 +139,15 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
subscription_plan: selectedPlan,
use_trial: useTrial,
payment_method_id: paymentMethodId,
// Include consent data
terms_accepted: formData.acceptTerms,
privacy_accepted: formData.acceptTerms,
marketing_consent: formData.marketingConsent,
analytics_consent: formData.analyticsConsent,
};
await register(registrationData);
showSuccessToast(t('auth:register.registering', '¡Bienvenido! Tu cuenta ha sido creada correctamente.'), {
title: t('auth:alerts.success_create', 'Cuenta creada exitosamente')
});
@@ -407,15 +416,47 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
/>
<label htmlFor="acceptTerms" className="text-sm text-text-secondary cursor-pointer">
Acepto los{' '}
<a href="#" className="text-color-primary hover:text-color-primary-dark underline">
<a href="/terms" target="_blank" rel="noopener noreferrer" className="text-color-primary hover:text-color-primary-dark underline">
términos y condiciones
</a>{' '}
de uso
y la{' '}
<a href="/privacy" target="_blank" rel="noopener noreferrer" className="text-color-primary hover:text-color-primary-dark underline">
política de privacidad
</a>{' '}
<span className="text-color-error">*</span>
</label>
</div>
{errors.acceptTerms && (
<p className="text-color-error text-sm ml-7">{errors.acceptTerms}</p>
)}
<div className="flex items-start space-x-3">
<input
type="checkbox"
id="marketingConsent"
checked={formData.marketingConsent}
onChange={handleInputChange('marketingConsent')}
className="mt-1 h-4 w-4 rounded border-border-primary text-color-primary focus:ring-color-primary focus:ring-offset-0"
disabled={isLoading}
/>
<label htmlFor="marketingConsent" className="text-sm text-text-secondary cursor-pointer">
Deseo recibir comunicaciones de marketing y promociones
</label>
</div>
<div className="flex items-start space-x-3">
<input
type="checkbox"
id="analyticsConsent"
checked={formData.analyticsConsent}
onChange={handleInputChange('analyticsConsent')}
className="mt-1 h-4 w-4 rounded border-border-primary text-color-primary focus:ring-color-primary focus:ring-offset-0"
disabled={isLoading}
/>
<label htmlFor="analyticsConsent" className="text-sm text-text-secondary cursor-pointer">
Acepto el uso de cookies analíticas para mejorar la experiencia
</label>
</div>
</div>
<div className="flex justify-end pt-4">

View File

@@ -0,0 +1,167 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '../Button';
import { X, Cookie, Settings } from 'lucide-react';
import { clsx } from 'clsx';
import { CookiePreferences, getCookieConsent, saveCookieConsent } from './cookieUtils';
interface CookieBannerProps {
onPreferencesClick?: () => void;
}
export const CookieBanner: React.FC<CookieBannerProps> = ({ onPreferencesClick }) => {
const { t } = useTranslation();
const [isVisible, setIsVisible] = useState(false);
const [isMinimized, setIsMinimized] = useState(false);
useEffect(() => {
const consent = getCookieConsent();
if (!consent) {
setIsVisible(true);
}
}, []);
const handleAcceptAll = () => {
const preferences: CookiePreferences = {
essential: true,
analytics: true,
marketing: true,
preferences: true,
timestamp: new Date().toISOString(),
version: '1.0'
};
saveCookieConsent(preferences);
setIsVisible(false);
};
const handleAcceptEssential = () => {
const preferences: CookiePreferences = {
essential: true,
analytics: false,
marketing: false,
preferences: false,
timestamp: new Date().toISOString(),
version: '1.0'
};
saveCookieConsent(preferences);
setIsVisible(false);
};
const handleCustomize = () => {
if (onPreferencesClick) {
onPreferencesClick();
}
setIsVisible(false);
};
if (!isVisible) return null;
return (
<div
className={clsx(
'fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 shadow-lg transition-all duration-300',
isMinimized ? 'h-16' : 'h-auto'
)}
role="dialog"
aria-label={t('common:cookie.banner_title', 'Cookie Consent')}
>
<div className="max-w-7xl mx-auto px-4 py-4">
{isMinimized ? (
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Cookie className="w-5 h-5 text-amber-600" />
<p className="text-sm text-gray-700 dark:text-gray-300">
{t('common:cookie.minimized_message', 'We use cookies to enhance your experience.')}
</p>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => setIsMinimized(false)}
>
{t('common:cookie.show_details', 'Show Details')}
</Button>
</div>
) : (
<div className="space-y-4">
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-3 flex-1">
<Cookie className="w-6 h-6 text-amber-600 mt-1 flex-shrink-0" />
<div className="flex-1">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
{t('common:cookie.banner_title', 'We Value Your Privacy')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed">
{t(
'common:cookie.banner_description',
'We use cookies and similar technologies to provide, protect, and improve our services. Some cookies are essential for the site to function, while others help us understand how you use our services and provide personalized features.'
)}
</p>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-2">
{t(
'common:cookie.banner_description_2',
'By clicking "Accept All", you consent to our use of all cookies. You can customize your preferences or accept only essential cookies.'
)}
</p>
<div className="mt-3">
<a
href="/cookies"
className="text-sm text-primary-600 hover:text-primary-700 underline"
target="_blank"
rel="noopener noreferrer"
>
{t('common:cookie.learn_more', 'Learn more about cookies')}
</a>
{' | '}
<a
href="/privacy"
className="text-sm text-primary-600 hover:text-primary-700 underline"
target="_blank"
rel="noopener noreferrer"
>
{t('common:cookie.privacy_policy', 'Privacy Policy')}
</a>
</div>
</div>
</div>
<button
onClick={() => setIsMinimized(true)}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
aria-label={t('common:cookie.minimize', 'Minimize')}
>
<X className="w-5 h-5" />
</button>
</div>
<div className="flex flex-col sm:flex-row gap-3 pt-2">
<Button
onClick={handleAcceptAll}
variant="primary"
className="flex-1 sm:flex-none"
>
{t('common:cookie.accept_all', 'Accept All')}
</Button>
<Button
onClick={handleAcceptEssential}
variant="outline"
className="flex-1 sm:flex-none"
>
{t('common:cookie.accept_essential', 'Essential Only')}
</Button>
<Button
onClick={handleCustomize}
variant="ghost"
className="flex-1 sm:flex-none"
>
<Settings className="w-4 h-4 mr-2" />
{t('common:cookie.customize', 'Customize')}
</Button>
</div>
</div>
)}
</div>
</div>
);
};
export default CookieBanner;

View File

@@ -0,0 +1,101 @@
export interface CookiePreferences {
essential: boolean;
analytics: boolean;
marketing: boolean;
preferences: boolean;
timestamp: string;
version: string;
}
const COOKIE_CONSENT_KEY = 'bakery_cookie_consent';
const COOKIE_CONSENT_VERSION = '1.0';
export const getCookieConsent = (): CookiePreferences | null => {
try {
const stored = localStorage.getItem(COOKIE_CONSENT_KEY);
if (!stored) return null;
const consent = JSON.parse(stored) as CookiePreferences;
if (consent.version !== COOKIE_CONSENT_VERSION) {
return null;
}
return consent;
} catch (error) {
console.error('Error reading cookie consent:', error);
return null;
}
};
export const saveCookieConsent = (preferences: CookiePreferences): void => {
try {
localStorage.setItem(COOKIE_CONSENT_KEY, JSON.stringify(preferences));
initializeAnalytics(preferences);
initializeMarketing(preferences);
} catch (error) {
console.error('Error saving cookie consent:', error);
}
};
export const clearCookieConsent = (): void => {
try {
localStorage.removeItem(COOKIE_CONSENT_KEY);
} catch (error) {
console.error('Error clearing cookie consent:', error);
}
};
export const hasConsent = (category: keyof Omit<CookiePreferences, 'timestamp' | 'version'>): boolean => {
const consent = getCookieConsent();
if (!consent) return false;
return consent[category];
};
const initializeAnalytics = (preferences: CookiePreferences): void => {
if (preferences.analytics) {
console.log('Analytics cookies enabled');
} else {
console.log('Analytics cookies disabled');
}
};
const initializeMarketing = (preferences: CookiePreferences): void => {
if (preferences.marketing) {
console.log('Marketing cookies enabled');
} else {
console.log('Marketing cookies disabled');
}
};
export const getCookieCategories = () => [
{
id: 'essential',
name: 'Essential Cookies',
description: 'Required for the website to function. These cookies enable core functionality such as security, authentication, and session management. They cannot be disabled.',
required: true,
examples: ['Session tokens', 'Authentication cookies', 'Security tokens']
},
{
id: 'preferences',
name: 'Preference Cookies',
description: 'Allow the website to remember information that changes the way the website behaves or looks, such as your preferred language or region.',
required: false,
examples: ['Language preference', 'Theme preference', 'Region settings']
},
{
id: 'analytics',
name: 'Analytics Cookies',
description: 'Help us understand how visitors interact with our website by collecting and reporting information anonymously.',
required: false,
examples: ['Google Analytics', 'Page views', 'User behavior tracking']
},
{
id: 'marketing',
name: 'Marketing Cookies',
description: 'Used to track visitors across websites to display relevant advertisements and marketing campaigns.',
required: false,
examples: ['Advertising cookies', 'Retargeting pixels', 'Social media cookies']
}
];

View File

@@ -0,0 +1,3 @@
export { CookieBanner } from './CookieBanner';
export { getCookieConsent, saveCookieConsent, clearCookieConsent, hasConsent, getCookieCategories } from './cookieUtils';
export type { CookiePreferences } from './cookieUtils';