Skip to content

Commit 2090987

Browse files
Implement GDPR compliance.
1 parent 60d18c0 commit 2090987

File tree

7 files changed

+771
-89
lines changed

7 files changed

+771
-89
lines changed

src/App.tsx

Lines changed: 49 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,66 @@
1-
2-
import { Toaster } from "@/components/ui/toaster";
3-
import { Toaster as Sonner } from "@/components/ui/sonner";
4-
import { TooltipProvider } from "@/components/ui/tooltip";
5-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
6-
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
7-
import { useEffect, lazy, Suspense } from "react";
8-
import { ErrorBoundary } from "@/components/ErrorBoundary";
9-
import { HelmetProvider } from 'react-helmet-async';
1+
import { Suspense, lazy } from 'react';
2+
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
103
import { AuthProvider } from '@/contexts/AuthContext';
11-
import './i18n';
4+
import { Toaster } from '@/components/ui/toaster';
5+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
6+
import { Helmet, HelmetProvider } from 'react-helmet-async';
7+
import { CookieBanner } from '@/components/CookieBanner';
8+
import { ErrorBoundary } from '@/components/ErrorBoundary';
9+
import '@/i18n';
10+
import PrivacyPage from '@/pages/PrivacyPage';
11+
import LegalPage from '@/pages/LegalPage';
1212

13-
// Lazy loading des composants de page
14-
const Index = lazy(() => import("./pages/Index"));
15-
const NotFound = lazy(() => import("./pages/NotFound"));
16-
const AffiliatePage = lazy(() => import("./pages/AffiliatePage"));
17-
const TrackingPage = lazy(() => import("./pages/TrackingPage"));
18-
const ShortLinkPage = lazy(() => import("./pages/ShortLinkPage"));
19-
const LandingPage = lazy(() => import("./pages/LandingPage"));
20-
const PricingPage = lazy(() => import("./pages/PricingPage"));
21-
const TrackingJsRoute = lazy(() => import("./pages/TrackingJsRoute"));
22-
const TestProductsPage = lazy(() => import("./pages/TestProductsPage"));
23-
const TestThankYouPage = lazy(() => import("./pages/TestThankYouPage"));
24-
const AdvancedStatsPage = lazy(() => import("./pages/AdvancedStatsPage"));
25-
const PaymentSuccessPage = lazy(() => import("./pages/PaymentSuccessPage"));
13+
const Index = lazy(() => import('@/pages/Index'));
14+
const Dashboard = lazy(() => import('@/components/Dashboard'));
15+
const AdvancedStatsPage = lazy(() => import('@/pages/AdvancedStatsPage'));
16+
const AffiliatePage = lazy(() => import('@/pages/AffiliatePage'));
17+
const ShortLinkTrackingPage = lazy(() => import('@/pages/ShortLinkTrackingPage'));
2618

27-
// QueryClient simplifié
2819
const queryClient = new QueryClient({
2920
defaultOptions: {
3021
queries: {
31-
staleTime: 30 * 1000, // 30 secondes seulement
32-
retry: 1, // Moins de retry
22+
retry: 1,
3323
refetchOnWindowFocus: false,
3424
},
3525
},
3626
});
3727

38-
// Composant de loading ultra-simple
39-
const FastLoader = () => (
40-
<div className="min-h-screen bg-white flex items-center justify-center">
41-
<div className="w-8 h-8 border-2 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
42-
</div>
43-
);
44-
45-
const DomainRouter = () => {
46-
const hostname = window.location.hostname;
47-
const currentPath = window.location.pathname;
48-
49-
useEffect(() => {
50-
if (hostname === 'dashboard.refspring.com' && currentPath === '/') {
51-
window.location.replace('/dashboard');
52-
}
53-
}, [hostname, currentPath]);
54-
28+
function App() {
5529
return (
5630
<ErrorBoundary>
57-
<Suspense fallback={<FastLoader />}>
58-
<Routes>
59-
<Route path="/" element={
60-
hostname === 'dashboard.refspring.com'
61-
? <Navigate to="/dashboard" replace />
62-
: <LandingPage />
63-
} />
64-
65-
<Route path="/pricing" element={<PricingPage />} />
66-
<Route path="/dashboard" element={<Index />} />
67-
<Route path="/advanced-stats" element={<AdvancedStatsPage />} />
68-
<Route path="/advanced-stats/:campaignId" element={<AdvancedStatsPage />} />
69-
70-
<Route path="/payment-success" element={<PaymentSuccessPage />} />
71-
72-
<Route path="/tracking.js" element={<TrackingJsRoute />} />
73-
74-
<Route path="/test-products" element={<TestProductsPage />} />
75-
<Route path="/test-thankyou" element={<TestThankYouPage />} />
76-
77-
<Route path="/r/:campaignId" element={<AffiliatePage />} />
78-
<Route path="/track/:campaignId/:affiliateId" element={<TrackingPage />} />
79-
<Route path="/s/:shortCode" element={<ShortLinkPage />} />
80-
81-
<Route path="*" element={<NotFound />} />
82-
</Routes>
83-
</Suspense>
84-
</ErrorBoundary>
85-
);
86-
};
87-
88-
const App = () => (
89-
<ErrorBoundary>
90-
<HelmetProvider>
91-
<AuthProvider>
31+
<HelmetProvider>
9232
<QueryClientProvider client={queryClient}>
93-
<TooltipProvider>
94-
<Toaster />
95-
<Sonner />
96-
<BrowserRouter>
97-
<DomainRouter />
98-
</BrowserRouter>
99-
</TooltipProvider>
33+
<AuthProvider>
34+
<Router>
35+
<Helmet>
36+
<title>RefSpring - Plateforme d'affiliation</title>
37+
<meta name="description" content="Gérez vos programmes d'affiliation avec RefSpring" />
38+
</Helmet>
39+
40+
<div className="App">
41+
<Suspense fallback={<div className="min-h-screen bg-white flex items-center justify-center">
42+
<div className="animate-spin rounded-full h-8 w-8 border-2 border-blue-600 border-t-transparent"></div>
43+
</div>}>
44+
<Routes>
45+
<Route path="/" element={<Index />} />
46+
<Route path="/dashboard" element={<Dashboard />} />
47+
<Route path="/campaign/:campaignId" element={<AdvancedStatsPage />} />
48+
<Route path="/affiliate/:affiliateId" element={<AffiliatePage />} />
49+
<Route path="/s/:shortCode" element={<ShortLinkTrackingPage />} />
50+
<Route path="/privacy" element={<PrivacyPage />} />
51+
<Route path="/legal" element={<LegalPage />} />
52+
</Routes>
53+
</Suspense>
54+
55+
<CookieBanner />
56+
<Toaster />
57+
</div>
58+
</Router>
59+
</AuthProvider>
10060
</QueryClientProvider>
101-
</AuthProvider>
102-
</HelmetProvider>
103-
</ErrorBoundary>
104-
);
61+
</HelmetProvider>
62+
</ErrorBoundary>
63+
);
64+
}
10565

10666
export default App;

src/components/CookieBanner.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
2+
import { Button } from '@/components/ui/button';
3+
import { Card } from '@/components/ui/card';
4+
import { useCookieConsent } from '@/hooks/useCookieConsent';
5+
import { Link } from 'react-router-dom';
6+
import { Cookie, Settings } from 'lucide-react';
7+
import { useState } from 'react';
8+
import { CookiePreferencesDialog } from '@/components/CookiePreferencesDialog';
9+
10+
export const CookieBanner = () => {
11+
const { showBanner, acceptAll, acceptNecessaryOnly } = useCookieConsent();
12+
const [showPreferences, setShowPreferences] = useState(false);
13+
14+
if (!showBanner) return null;
15+
16+
return (
17+
<>
18+
<div className="fixed bottom-0 left-0 right-0 z-50 p-4 bg-black/50 backdrop-blur-sm">
19+
<Card className="max-w-4xl mx-auto p-6 bg-white shadow-xl border border-slate-200">
20+
<div className="flex flex-col lg:flex-row gap-4 items-start lg:items-center">
21+
<div className="flex items-start gap-3 flex-1">
22+
<Cookie className="h-6 w-6 text-blue-600 flex-shrink-0 mt-1" />
23+
<div className="space-y-2">
24+
<h3 className="font-semibold text-slate-900">
25+
Respect de votre vie privée
26+
</h3>
27+
<p className="text-sm text-slate-600 leading-relaxed">
28+
Nous utilisons des cookies pour améliorer votre expérience, analyser le trafic et personnaliser le contenu.
29+
En cliquant sur "Accepter tout", vous consentez à l'utilisation de tous les cookies.
30+
Vous pouvez modifier vos préférences à tout moment.
31+
</p>
32+
<div className="flex gap-4 text-xs">
33+
<Link to="/privacy" className="text-blue-600 hover:underline">
34+
Politique de confidentialité
35+
</Link>
36+
<Link to="/legal" className="text-blue-600 hover:underline">
37+
Mentions légales
38+
</Link>
39+
</div>
40+
</div>
41+
</div>
42+
43+
<div className="flex flex-col sm:flex-row gap-2 lg:flex-shrink-0">
44+
<Button
45+
variant="outline"
46+
size="sm"
47+
onClick={() => setShowPreferences(true)}
48+
className="flex items-center gap-2"
49+
>
50+
<Settings className="h-4 w-4" />
51+
Personnaliser
52+
</Button>
53+
<Button
54+
variant="outline"
55+
size="sm"
56+
onClick={acceptNecessaryOnly}
57+
>
58+
Nécessaires uniquement
59+
</Button>
60+
<Button
61+
size="sm"
62+
onClick={acceptAll}
63+
className="bg-blue-600 hover:bg-blue-700 text-white"
64+
>
65+
Accepter tout
66+
</Button>
67+
</div>
68+
</div>
69+
</Card>
70+
</div>
71+
72+
<CookiePreferencesDialog
73+
open={showPreferences}
74+
onOpenChange={setShowPreferences}
75+
/>
76+
</>
77+
);
78+
};
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
2+
import { useState, useEffect } from 'react';
3+
import { Button } from '@/components/ui/button';
4+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
5+
import { Switch } from '@/components/ui/switch';
6+
import { Label } from '@/components/ui/label';
7+
import { useCookieConsent, CookiePreferences } from '@/hooks/useCookieConsent';
8+
import { Cookie, Shield, BarChart, Target } from 'lucide-react';
9+
10+
interface CookiePreferencesDialogProps {
11+
open: boolean;
12+
onOpenChange: (open: boolean) => void;
13+
}
14+
15+
export const CookiePreferencesDialog = ({ open, onOpenChange }: CookiePreferencesDialogProps) => {
16+
const { preferences, updatePreferences, acceptAll, acceptNecessaryOnly } = useCookieConsent();
17+
const [localPreferences, setLocalPreferences] = useState<CookiePreferences>(preferences);
18+
19+
useEffect(() => {
20+
setLocalPreferences(preferences);
21+
}, [preferences]);
22+
23+
const handleSave = () => {
24+
updatePreferences(localPreferences);
25+
onOpenChange(false);
26+
};
27+
28+
const handleAcceptAll = () => {
29+
acceptAll();
30+
onOpenChange(false);
31+
};
32+
33+
const handleNecessaryOnly = () => {
34+
acceptNecessaryOnly();
35+
onOpenChange(false);
36+
};
37+
38+
return (
39+
<Dialog open={open} onOpenChange={onOpenChange}>
40+
<DialogContent className="sm:max-w-[500px] max-h-[80vh] overflow-y-auto">
41+
<DialogHeader>
42+
<DialogTitle className="flex items-center gap-2">
43+
<Cookie className="h-5 w-5" />
44+
Préférences des cookies
45+
</DialogTitle>
46+
<DialogDescription>
47+
Gérez vos préférences de cookies. Vous pouvez modifier ces paramètres à tout moment.
48+
</DialogDescription>
49+
</DialogHeader>
50+
51+
<div className="space-y-6 py-4">
52+
{/* Cookies nécessaires */}
53+
<div className="space-y-3">
54+
<div className="flex items-center justify-between">
55+
<div className="flex items-center gap-2">
56+
<Shield className="h-4 w-4 text-green-600" />
57+
<Label className="font-medium">Cookies nécessaires</Label>
58+
</div>
59+
<Switch checked={true} disabled />
60+
</div>
61+
<p className="text-sm text-slate-600 ml-6">
62+
Ces cookies sont essentiels au fonctionnement du site. Ils permettent des fonctionnalités de base comme la navigation et l'accès aux zones sécurisées.
63+
</p>
64+
</div>
65+
66+
{/* Cookies d'analyse */}
67+
<div className="space-y-3">
68+
<div className="flex items-center justify-between">
69+
<div className="flex items-center gap-2">
70+
<BarChart className="h-4 w-4 text-blue-600" />
71+
<Label htmlFor="analytics" className="font-medium">Cookies d'analyse</Label>
72+
</div>
73+
<Switch
74+
id="analytics"
75+
checked={localPreferences.analytics}
76+
onCheckedChange={(checked) =>
77+
setLocalPreferences(prev => ({ ...prev, analytics: checked }))
78+
}
79+
/>
80+
</div>
81+
<p className="text-sm text-slate-600 ml-6">
82+
Ces cookies nous aident à comprendre comment vous utilisez notre site en collectant des informations anonymes sur votre navigation.
83+
</p>
84+
</div>
85+
86+
{/* Cookies marketing */}
87+
<div className="space-y-3">
88+
<div className="flex items-center justify-between">
89+
<div className="flex items-center gap-2">
90+
<Target className="h-4 w-4 text-purple-600" />
91+
<Label htmlFor="marketing" className="font-medium">Cookies marketing</Label>
92+
</div>
93+
<Switch
94+
id="marketing"
95+
checked={localPreferences.marketing}
96+
onCheckedChange={(checked) =>
97+
setLocalPreferences(prev => ({ ...prev, marketing: checked }))
98+
}
99+
/>
100+
</div>
101+
<p className="text-sm text-slate-600 ml-6">
102+
Ces cookies sont utilisés pour vous proposer des publicités pertinentes et mesurer l'efficacité de nos campagnes marketing.
103+
</p>
104+
</div>
105+
</div>
106+
107+
<DialogFooter className="flex flex-col sm:flex-row gap-2">
108+
<Button variant="outline" onClick={handleNecessaryOnly} className="w-full sm:w-auto">
109+
Nécessaires uniquement
110+
</Button>
111+
<Button variant="outline" onClick={handleAcceptAll} className="w-full sm:w-auto">
112+
Accepter tout
113+
</Button>
114+
<Button onClick={handleSave} className="w-full sm:w-auto bg-blue-600 hover:bg-blue-700">
115+
Sauvegarder mes choix
116+
</Button>
117+
</DialogFooter>
118+
</DialogContent>
119+
</Dialog>
120+
);
121+
};

0 commit comments

Comments
 (0)