Skip to content

Commit 7c50a9c

Browse files
feat: Implement onboarding carousel
Adds a full-screen onboarding carousel to the dashboard, guiding new users. Includes a 4-slide structure with illustrations, titles, and text, matching the dashboard design. Implements a system to track user's onboarding status and integrates the carousel into the authentication flow.
1 parent b4e74d3 commit 7c50a9c

File tree

3 files changed

+209
-2
lines changed

3 files changed

+209
-2
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
2+
import { useState } from 'react';
3+
import { Button } from '@/components/ui/button';
4+
import { Card, CardContent } from '@/components/ui/card';
5+
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/components/ui/carousel';
6+
import { DashboardBackground } from '@/components/DashboardBackground';
7+
import { RefSpringLogo } from '@/components/RefSpringLogo';
8+
import { CheckCircle, Shield, TrendingUp, Rocket } from 'lucide-react';
9+
10+
interface OnboardingCarouselProps {
11+
onComplete: () => void;
12+
}
13+
14+
const slides = [
15+
{
16+
id: 1,
17+
icon: TrendingUp,
18+
title: "Commission de 2,5%",
19+
subtitle: "Notre modèle économique",
20+
description: "RefSpring prend une commission de 2,5% sur votre chiffre d'affaires généré via les affiliés. Pas d'abonnement, pas de frais cachés.",
21+
color: "from-green-500 to-emerald-600"
22+
},
23+
{
24+
id: 2,
25+
icon: Shield,
26+
title: "Outil de tracking uniquement",
27+
subtitle: "Notre périmètre d'action",
28+
description: "RefSpring est un outil de suivi et d'analyse. Nous ne gérons pas les paiements entre vous et vos affiliés.",
29+
color: "from-blue-500 to-cyan-600"
30+
},
31+
{
32+
id: 3,
33+
icon: CheckCircle,
34+
title: "Votre responsabilité",
35+
subtitle: "Transparence totale",
36+
description: "Il est de votre responsabilité de payer vos affiliés. RefSpring se dégage de tout litige pouvant survenir en cas de non-paiement des commissions.",
37+
color: "from-orange-500 to-red-600"
38+
},
39+
{
40+
id: 4,
41+
icon: Rocket,
42+
title: "Bienvenue sur RefSpring !",
43+
subtitle: "Prêt à commencer ?",
44+
description: "Créez votre première campagne et commencez à suivre vos performances d'affiliation en temps réel.",
45+
color: "from-purple-500 to-indigo-600"
46+
}
47+
];
48+
49+
export const OnboardingCarousel = ({ onComplete }: OnboardingCarouselProps) => {
50+
const [currentSlide, setCurrentSlide] = useState(0);
51+
52+
return (
53+
<div className="min-h-screen bg-gradient-to-br from-blue-50/50 via-white to-purple-50/50 relative overflow-hidden">
54+
<DashboardBackground />
55+
56+
<div className="relative z-10 min-h-screen flex items-center justify-center px-4">
57+
<div className="w-full max-w-4xl">
58+
{/* Header avec logo */}
59+
<div className="text-center mb-8">
60+
<div className="flex items-center justify-center gap-3 mb-4">
61+
<RefSpringLogo width="48" height="48" />
62+
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
63+
RefSpring
64+
</h1>
65+
</div>
66+
<p className="text-slate-600">Découvrez comment fonctionne votre plateforme d'affiliation</p>
67+
</div>
68+
69+
<Carousel className="w-full" onSelect={(index) => setCurrentSlide(index || 0)}>
70+
<CarouselContent>
71+
{slides.map((slide, index) => {
72+
const IconComponent = slide.icon;
73+
return (
74+
<CarouselItem key={slide.id}>
75+
<Card className="bg-white/80 backdrop-blur-sm border-slate-200/50 shadow-2xl">
76+
<CardContent className="p-12 text-center">
77+
{/* Icône avec gradient */}
78+
<div className={`w-24 h-24 mx-auto mb-8 bg-gradient-to-r ${slide.color} rounded-full flex items-center justify-center shadow-xl`}>
79+
<IconComponent className="w-12 h-12 text-white" />
80+
</div>
81+
82+
{/* Contenu */}
83+
<div className="space-y-6">
84+
<div>
85+
<h2 className="text-3xl font-bold text-slate-900 mb-2">
86+
{slide.title}
87+
</h2>
88+
<p className="text-lg text-slate-600 font-medium">
89+
{slide.subtitle}
90+
</p>
91+
</div>
92+
93+
<p className="text-lg text-slate-700 leading-relaxed max-w-2xl mx-auto">
94+
{slide.description}
95+
</p>
96+
</div>
97+
98+
{/* Bouton sur la dernière slide */}
99+
{index === slides.length - 1 && (
100+
<div className="mt-8">
101+
<Button
102+
size="lg"
103+
className="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white px-8 py-4 text-lg shadow-xl hover:shadow-2xl transition-all"
104+
onClick={onComplete}
105+
>
106+
Commencer l'aventure
107+
<Rocket className="ml-2 h-5 w-5" />
108+
</Button>
109+
</div>
110+
)}
111+
</CardContent>
112+
</Card>
113+
</CarouselItem>
114+
);
115+
})}
116+
</CarouselContent>
117+
118+
{/* Navigation */}
119+
<div className="flex justify-center items-center gap-4 mt-8">
120+
<CarouselPrevious className="static translate-y-0" />
121+
122+
{/* Points indicateurs */}
123+
<div className="flex gap-2">
124+
{slides.map((_, index) => (
125+
<div
126+
key={index}
127+
className={`w-3 h-3 rounded-full transition-all duration-300 ${
128+
index === currentSlide
129+
? 'bg-blue-600 w-8'
130+
: 'bg-slate-300 hover:bg-slate-400'
131+
}`}
132+
/>
133+
))}
134+
</div>
135+
136+
<CarouselNext className="static translate-y-0" />
137+
</div>
138+
</Carousel>
139+
140+
{/* Bouton passer */}
141+
<div className="text-center mt-6">
142+
<Button
143+
variant="link"
144+
onClick={onComplete}
145+
className="text-slate-500 hover:text-slate-700"
146+
>
147+
Passer l'introduction
148+
</Button>
149+
</div>
150+
</div>
151+
</div>
152+
</div>
153+
);
154+
};

src/hooks/useOnboarding.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
import { useState, useEffect } from 'react';
3+
import { useAuth } from '@/hooks/useAuth';
4+
5+
export const useOnboarding = () => {
6+
const { user } = useAuth();
7+
const [hasSeenOnboarding, setHasSeenOnboarding] = useState<boolean | null>(null);
8+
const [loading, setLoading] = useState(true);
9+
10+
useEffect(() => {
11+
if (!user) {
12+
setLoading(false);
13+
return;
14+
}
15+
16+
// Clé unique par utilisateur
17+
const storageKey = `onboarding_completed_${user.uid}`;
18+
const completed = localStorage.getItem(storageKey) === 'true';
19+
20+
setHasSeenOnboarding(completed);
21+
setLoading(false);
22+
}, [user]);
23+
24+
const markOnboardingCompleted = () => {
25+
if (!user) return;
26+
27+
const storageKey = `onboarding_completed_${user.uid}`;
28+
localStorage.setItem(storageKey, 'true');
29+
setHasSeenOnboarding(true);
30+
};
31+
32+
return {
33+
hasSeenOnboarding,
34+
markOnboardingCompleted,
35+
loading
36+
};
37+
};

src/pages/Index.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11

22
import { useAuth } from '@/hooks/useAuth';
3+
import { useOnboarding } from '@/hooks/useOnboarding';
34
import { AuthForm } from '@/components/AuthForm';
45
import { Dashboard } from '@/components/Dashboard';
6+
import { OnboardingCarousel } from '@/components/OnboardingCarousel';
57
import { useTranslation } from 'react-i18next';
68

79
const Index = () => {
8-
const { user, loading } = useAuth();
10+
const { user, loading: authLoading } = useAuth();
11+
const { hasSeenOnboarding, markOnboardingCompleted, loading: onboardingLoading } = useOnboarding();
912
const { t } = useTranslation();
1013

14+
const loading = authLoading || onboardingLoading;
15+
1116
if (loading) {
1217
return (
1318
<div className="min-h-screen bg-white flex items-center justify-center">
@@ -19,7 +24,18 @@ const Index = () => {
1924
);
2025
}
2126

22-
return user ? <Dashboard /> : <AuthForm />;
27+
// Si pas connecté, afficher le formulaire d'auth
28+
if (!user) {
29+
return <AuthForm />;
30+
}
31+
32+
// Si connecté mais n'a pas vu l'onboarding, afficher le carousel
33+
if (!hasSeenOnboarding) {
34+
return <OnboardingCarousel onComplete={markOnboardingCompleted} />;
35+
}
36+
37+
// Sinon afficher le dashboard
38+
return <Dashboard />;
2339
};
2440

2541
export default Index;

0 commit comments

Comments
 (0)