Skip to content

Commit acf3b5f

Browse files
implemented onboarding feature
1 parent 1088f7c commit acf3b5f

File tree

10 files changed

+1639
-35
lines changed

10 files changed

+1639
-35
lines changed

package-lock.json

Lines changed: 209 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"lucide-react": "^0.544.0",
2929
"react": "^19.1.1",
3030
"react-dom": "^19.1.1",
31+
"react-joyride": "^2.9.3",
3132
"react-markdown": "^10.1.0",
3233
"react-responsive-carousel": "^3.2.23",
3334
"react-router-dom": "^7.9.3",

src/components/Onboarding.tsx

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import React from 'react';
2+
import {
3+
OnboardingProvider,
4+
useOnboarding,
5+
} from '@/contexts/OnboardingContext';
6+
import { getOnboardingStepsForPage } from '@/constants/OnboardingSteps';
7+
8+
interface OnboardingProps {
9+
children: React.ReactNode;
10+
pageType?: 'homepage' | 'try' | 'about' | 'news';
11+
showProgressIndicator?: boolean;
12+
customSteps?: any[];
13+
}
14+
15+
export const Onboarding: React.FC<OnboardingProps> = ({
16+
children,
17+
pageType = 'homepage',
18+
showProgressIndicator = true,
19+
customSteps,
20+
}) => {
21+
const steps = customSteps || getOnboardingStepsForPage(pageType);
22+
23+
return (
24+
<OnboardingProvider steps={steps}>
25+
<OnboardingContent showProgressIndicator={showProgressIndicator}>
26+
{children}
27+
</OnboardingContent>
28+
</OnboardingProvider>
29+
);
30+
};
31+
32+
const OnboardingContent: React.FC<{
33+
children: React.ReactNode;
34+
showProgressIndicator: boolean;
35+
}> = ({ children, showProgressIndicator }) => {
36+
const { isOnboardingActive, onboardingProgress, resetOnboarding } =
37+
useOnboarding();
38+
39+
return (
40+
<div className="relative">
41+
{children}
42+
43+
{/* Optional Progress Indicator */}
44+
{showProgressIndicator && isOnboardingActive && (
45+
<div className="fixed top-4 right-4 z-[9999] bg-white rounded-lg shadow-lg border p-3 min-w-[200px]">
46+
<div className="flex items-center justify-between mb-2">
47+
<span className="text-sm font-medium text-gray-700">
48+
Tour Progress
49+
</span>
50+
<button
51+
onClick={resetOnboarding}
52+
className="text-xs text-gray-500 hover:text-gray-700 underline"
53+
title="Reset tour progress"
54+
>
55+
Reset
56+
</button>
57+
</div>
58+
<div className="w-full bg-gray-200 rounded-full h-2">
59+
<div
60+
className="bg-blue-600 h-2 rounded-full transition-all duration-300 ease-out"
61+
style={{ width: `${onboardingProgress}%` }}
62+
/>
63+
</div>
64+
<div className="text-xs text-gray-500 mt-1 text-center">
65+
{Math.round(onboardingProgress)}% complete
66+
</div>
67+
</div>
68+
)}
69+
</div>
70+
);
71+
};
72+
73+
// useOnboarding hook is imported above
74+
75+
// Add data attributes to elements for targeting
76+
export const addOnboardingIds = {
77+
sugarLabsLogo: () => ({
78+
'data-onboarding-id': 'sugar-labs-logo',
79+
className: 'sugar-labs-logo',
80+
}),
81+
mainNavigation: () => ({
82+
'data-onboarding-id': 'main-navigation',
83+
className: 'main-navigation',
84+
}),
85+
trySugarButton: () => ({
86+
'data-onboarding-id': 'try-sugar-button',
87+
className: 'try-sugar-button',
88+
}),
89+
activitiesSection: () => ({
90+
'data-onboarding-id': 'activities-section',
91+
className: 'activities-section',
92+
}),
93+
statsSection: () => ({
94+
'data-onboarding-id': 'stats-section',
95+
className: 'stats-section',
96+
}),
97+
donationSection: () => ({
98+
'data-onboarding-id': 'donation-section',
99+
className: 'donation-section',
100+
}),
101+
};
102+
103+
// Component to add onboarding trigger button
104+
export const OnboardingTrigger: React.FC<{
105+
className?: string;
106+
children?: React.ReactNode;
107+
}> = ({ className = '', children = 'Start Tour' }) => {
108+
const { startOnboarding, onboardingCompleted } = useOnboarding();
109+
110+
if (onboardingCompleted) {
111+
return null; // Hide trigger if onboarding is completed
112+
}
113+
114+
return (
115+
<button
116+
onClick={startOnboarding}
117+
className={`inline-flex items-center px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors duration-200 ${className}`}
118+
title="Take a tour to learn about Sugar Labs features"
119+
>
120+
<div className="mr-2 text-lg">🎯</div>
121+
{children}
122+
</button>
123+
);
124+
};
125+
126+
// Custom tooltip wrapper component for styled onboarding tooltips
127+
export const OnboardingTooltipWrapper: React.FC<{
128+
title: string;
129+
content: React.ReactNode;
130+
icon?: string;
131+
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red';
132+
}> = ({ title, content, icon = '✨', accentColor = 'blue' }) => {
133+
const colorClasses = {
134+
blue: 'from-blue-50 to-blue-100 border-blue-200',
135+
green: 'from-green-50 to-green-100 border-green-200',
136+
purple: 'from-purple-50 to-purple-100 border-purple-200',
137+
orange: 'from-orange-50 to-orange-100 border-orange-200',
138+
red: 'from-red-50 to-red-100 border-red-200',
139+
};
140+
141+
const iconClasses = {
142+
blue: 'text-blue-600',
143+
green: 'text-green-600',
144+
purple: 'text-purple-600',
145+
orange: 'text-orange-600',
146+
red: 'text-red-600',
147+
};
148+
149+
return (
150+
<div
151+
className={`bg-gradient-to-br ${colorClasses[accentColor]} border rounded-xl p-6 shadow-lg`}
152+
>
153+
<div className="flex items-center mb-4">
154+
<span className={`text-2xl mr-3 ${iconClasses[accentColor]}`}>
155+
{icon}
156+
</span>
157+
<h3 className="text-xl font-bold text-gray-800">{title}</h3>
158+
</div>
159+
<div className="text-gray-700">{content}</div>
160+
</div>
161+
);
162+
};
163+
164+
// Helper component to render onboarding step content
165+
export const OnboardingStep: React.FC<{
166+
title: string;
167+
description: string;
168+
features?: string[];
169+
note?: string;
170+
accentColor?: 'blue' | 'green' | 'purple' | 'orange' | 'red';
171+
}> = ({ title, description, features = [], note }) => {
172+
return (
173+
<div className="space-y-4">
174+
<div className="space-y-3">
175+
<h3 className="text-xl font-semibold text-gray-800">{title}</h3>
176+
<p className="text-gray-600 leading-relaxed">{description}</p>
177+
178+
{features.length > 0 && (
179+
<ul className="space-y-2">
180+
{features.map((feature, index) => (
181+
<li key={index} className="flex items-start">
182+
<span className="text-blue-500 mr-2 mt-1"></span>
183+
<span className="text-gray-700">{feature}</span>
184+
</li>
185+
))}
186+
</ul>
187+
)}
188+
189+
{note && (
190+
<div className="mt-4 p-3 bg-blue-50 rounded-lg border border-blue-200">
191+
<p className="text-blue-800 font-medium text-sm">{note}</p>
192+
</div>
193+
)}
194+
</div>
195+
</div>
196+
);
197+
};
198+
199+
export default Onboarding;
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import React, { useState } from 'react';
2+
import { useOnboarding } from '@/contexts/OnboardingContext';
3+
4+
interface OnboardingTriggerProps {
5+
className?: string;
6+
children?: React.ReactNode;
7+
variant?: 'floating' | 'inline' | 'minimal';
8+
}
9+
10+
export const OnboardingTrigger: React.FC<OnboardingTriggerProps> = ({
11+
className = '',
12+
children = 'Take Tour',
13+
variant = 'floating',
14+
}) => {
15+
const {
16+
startOnboarding,
17+
onboardingCompleted,
18+
shouldShowTrigger,
19+
isOnboardingActive,
20+
} = useOnboarding();
21+
const [isVisible, setIsVisible] = useState(true);
22+
23+
// Call all hooks before any conditional returns
24+
const handleClick = () => {
25+
startOnboarding();
26+
setIsVisible(false);
27+
};
28+
29+
// Show trigger when tour is dismissed (not completed)
30+
React.useEffect(() => {
31+
if (onboardingCompleted) {
32+
setIsVisible(false); // Hide if completed
33+
} else if (shouldShowTrigger()) {
34+
setIsVisible(true); // Show if dismissed or first time
35+
}
36+
}, [onboardingCompleted, shouldShowTrigger]);
37+
38+
// Don't show trigger if onboarding is completed, currently running, or hidden
39+
if (!shouldShowTrigger() || isOnboardingActive || !isVisible) {
40+
return null;
41+
}
42+
43+
const baseClasses = {
44+
floating: `
45+
fixed bottom-6 right-6 z-[60]
46+
bg-gradient-to-r from-blue-600 to-blue-700
47+
text-white text-sm font-semibold
48+
rounded-full px-6 py-3
49+
shadow-2xl hover:shadow-3xl
50+
transform hover:scale-105 active:scale-95
51+
transition-all duration-300 ease-out
52+
flex items-center space-x-2
53+
border border-blue-500/20 backdrop-blur-sm
54+
${className}
55+
`,
56+
inline: `
57+
inline-flex items-center px-4 py-2
58+
bg-blue-600 text-white text-sm font-medium
59+
rounded-md hover:bg-blue-700
60+
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2
61+
transition-colors duration-200
62+
${className}
63+
`,
64+
minimal: `
65+
inline-flex items-center px-2 py-1
66+
text-blue-600 hover:text-blue-700
67+
text-sm font-medium underline
68+
transition-colors duration-200
69+
${className}
70+
`,
71+
};
72+
73+
return (
74+
<button
75+
onClick={handleClick}
76+
className={baseClasses[variant]}
77+
title="Take a guided tour to learn about Sugar Labs features"
78+
aria-label="Start onboarding tour"
79+
>
80+
<span className="text-lg">🎯</span>
81+
<span>{children}</span>
82+
</button>
83+
);
84+
};
85+
86+
// Floating Help Button for additional assistance
87+
export const FloatingHelpButton: React.FC<{
88+
className?: string;
89+
}> = ({ className = '' }) => {
90+
const [isExpanded, setIsExpanded] = useState(false);
91+
92+
return (
93+
<div className={`fixed bottom-6 left-6 z-40 ${className}`}>
94+
<div className="relative">
95+
{/* Expandable content */}
96+
<div
97+
className={`absolute bottom-full mb-4 left-0 w-80 bg-white rounded-lg shadow-xl border p-4 transition-all duration-300 ${isExpanded ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2 pointer-events-none'}`}
98+
>
99+
<h3 className="font-semibold text-gray-800 mb-2">Need Help?</h3>
100+
<p className="text-sm text-gray-600 mb-3">
101+
New to Sugar Labs? Take our quick tour to discover all the amazing
102+
features!
103+
</p>
104+
<div className="flex space-x-2">
105+
<OnboardingTrigger variant="inline" className="text-xs px-3 py-1">
106+
Start Tour
107+
</OnboardingTrigger>
108+
<button
109+
onClick={() => setIsExpanded(false)}
110+
className="text-xs text-gray-500 hover:text-gray-700 underline"
111+
>
112+
Maybe later
113+
</button>
114+
</div>
115+
</div>
116+
117+
{/* Main help button */}
118+
<button
119+
onClick={() => setIsExpanded(!isExpanded)}
120+
className="w-12 h-12 bg-gradient-to-r from-green-600 to-green-700 text-white rounded-full shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-300 flex items-center justify-center"
121+
title="Get help and support"
122+
aria-label="Help and support"
123+
>
124+
<span className="text-xl"></span>
125+
</button>
126+
</div>
127+
</div>
128+
);
129+
};
130+
131+
// Progress indicator component
132+
export const OnboardingProgress: React.FC<{
133+
className?: string;
134+
}> = ({ className = '' }) => {
135+
const { isOnboardingActive, currentStep, totalSteps, onboardingProgress } =
136+
useOnboarding();
137+
138+
if (!isOnboardingActive) return null;
139+
140+
return (
141+
<div
142+
className={`fixed top-4 right-4 z-50 bg-white rounded-lg shadow-lg border p-3 min-w-[200px] ${className}`}
143+
>
144+
<div className="flex items-center justify-between mb-2">
145+
<span className="text-sm font-medium text-gray-700">Tour Progress</span>
146+
<span className="text-xs text-gray-500">
147+
{currentStep + 1}/{totalSteps}
148+
</span>
149+
</div>
150+
<div className="w-full bg-gray-200 rounded-full h-2">
151+
<div
152+
className="bg-blue-600 h-2 rounded-full transition-all duration-300 ease-out"
153+
style={{ width: `${onboardingProgress}%` }}
154+
/>
155+
</div>
156+
<div className="text-xs text-gray-500 mt-1 text-center">
157+
{Math.round(onboardingProgress)}% complete
158+
</div>
159+
</div>
160+
);
161+
};
162+
163+
export default OnboardingTrigger;

0 commit comments

Comments
 (0)