Improve GDPR implementation
This commit is contained in:
@@ -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">
|
||||
|
||||
167
frontend/src/components/ui/CookieConsent/CookieBanner.tsx
Normal file
167
frontend/src/components/ui/CookieConsent/CookieBanner.tsx
Normal 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;
|
||||
101
frontend/src/components/ui/CookieConsent/cookieUtils.ts
Normal file
101
frontend/src/components/ui/CookieConsent/cookieUtils.ts
Normal 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']
|
||||
}
|
||||
];
|
||||
3
frontend/src/components/ui/CookieConsent/index.ts
Normal file
3
frontend/src/components/ui/CookieConsent/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { CookieBanner } from './CookieBanner';
|
||||
export { getCookieConsent, saveCookieConsent, clearCookieConsent, hasConsent, getCookieCategories } from './cookieUtils';
|
||||
export type { CookiePreferences } from './cookieUtils';
|
||||
Reference in New Issue
Block a user