Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions src/popup/components/onboarding/OnboardingFlow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import React, { useState } from 'react';
import { FiMonitor, FiShield, FiUsers, FiCheck, FiTrendingUp } from 'react-icons/fi';
import { useNavigate } from 'react-router-dom';

interface OnboardingFlowProps {
onComplete?: () => void;
}

const OnboardingFlow: React.FC<OnboardingFlowProps> = ({ onComplete }) => {
const [currentStep, setCurrentStep] = useState(0);
const navigate = useNavigate();

const steps = [
{
icon: (
<div className="w-20 h-20 rounded-2xl flex items-center justify-center mx-auto shadow-lg overflow-hidden">
<img
src="icons/icon128.png"
alt="BrowsePing Logo"
className="w-full h-full object-contain"
/>
</div>
),
title: "Welcome to BrowsePing",
description: "Browsing doesn't have to be lonely. Connect with friends, share your online presence, and discover what's capturing everyone's attention across the web.",
features: [
"See what friends are browsing",
"Share your activity in real-time",
"Discover content together"
]
},
{
icon: <FiMonitor className="w-16 h-16 text-blue-600" />,
title: "Powerful Analytics & Insights",
description: "Understand your digital habits with detailed analytics about your browsing patterns, time spent online, and most-visited sites.",
features: [
"Track your browsing time",
"Analyze tab usage patterns",
"View hourly activity insights"
]
},
{
icon: <FiTrendingUp className="w-16 h-16 text-blue-600" />,
title: "Monthly Leaderboard Competition",
description: "Compete with friends on the monthly activity leaderboard. See who's the most active browser and climb to the top!",
features: [
"Track monthly online activity",
"Compete with your network",
"Earn bragging rights"
]
},
{
icon: <FiShield className="w-16 h-16 text-blue-600" />,
title: "Complete Privacy Control",
description: "You decide what to share and with whom. Granular privacy settings giengage in real-time conversations.",
features: [
"Add and manage friends",
"Real-time messaging",
"Stay connected alwayyou want"
]
},
{
icon: <FiUsers className="w-16 h-16 text-blue-600" />,
title: "Connect & Engage",
description: "Build your social browsing network. Add friends, send messages, and compete on monthly activity leaderboards.",
features: [
"Add and manage friends",
"Real-time messaging",
"Monthly leaderboards"
]
},
{
icon: <FiCheck className="w-16 h-16 text-green-600" />,
title: "Ready to Get Started?",
description: "Join thousands making browsing more social. Create your free account now and start connecting with friends.",
features: [
"Quick & easy signup",
"Free forever",
"Join your friends today"
]
}
];

const handleNext = async () => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1);
} else {
if (onComplete) {
onComplete();
} else {
navigate('/welcome');
}
}
};

const handleBack = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};

const handleSkip = async () => {
if (onComplete) {
onComplete();
} else {
navigate('/welcome');
}
};

const currentStepData = steps[currentStep];

return (
<div className="min-h-[600px] w-[400px] bg-gradient-to-br from-blue-50 to-indigo-100 flex flex-col">
<div className="flex-1 flex flex-col items-center justify-center p-8">
<div className="text-center space-y-6">
<div className="flex justify-center mb-4">
{currentStepData.icon}
</div>

<div>
<h1 className="text-2xl font-bold text-gray-800 mb-3">
{currentStepData.title}
</h1>
<p className="text-gray-600 text-sm leading-relaxed max-w-sm mx-auto">
{currentStepData.description}
</p>
</div>

<div className="space-y-3 pt-4">
{currentStepData.features.map((feature, index) => (
<div
key={index}
className="flex items-center justify-center space-x-2 text-sm text-gray-700"
>
<div className="w-1.5 h-1.5 rounded-full bg-blue-600"></div>
<span>{feature}</span>
</div>
))}
</div>
</div>
</div>

<div className="p-6 space-y-4">
<div className="flex justify-center space-x-2 mb-4">
{steps.map((_, index) => (
<div
key={index}
className={`h-2 rounded-full transition-all duration-300 ${
index === currentStep
? 'w-8 bg-blue-600'
: 'w-2 bg-gray-300'
}`}
/>
))}
</div>

<div className="flex gap-3">
{currentStep > 0 && (
<button
onClick={handleBack}
className="flex-1 py-3 px-4 border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-50 transition duration-200"
>
Back
</button>
)}

{currentStep < steps.length - 1 && (
<button
onClick={handleSkip}
className="flex-1 py-3 px-4 border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-50 transition duration-200"
>
Skip
</button>
)}

<button
onClick={handleNext}
className="flex-1 py-3 px-4 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition duration-200"
>
{currentStep < steps.length - 1 ? 'Next' : 'Get Started'}
</button>
</div>

<div className="text-center text-xs text-gray-500">
Step {currentStep + 1} of {steps.length}
</div>
</div>
</div>
);
};

export default OnboardingFlow;
1 change: 1 addition & 0 deletions src/popup/components/onboarding/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as OnboardingFlow } from './OnboardingFlow';
21 changes: 14 additions & 7 deletions src/popup/components/welcome/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react';
import { FiUserPlus, FiClock, FiShield } from 'react-icons/fi';
import { FiUserPlus, FiClock, FiShield, FiCompass } from 'react-icons/fi';

const WelcomeScreen: React.FC = () => {
return (
<div className="min-h-[600px] w-[400px] bg-gradient-to-br from-blue-50 to-indigo-100 p-6 flex flex-col">
<div className="flex-1 flex flex-col items-center justify-center space-y-8">
<div className="h-[600px] w-[400px] bg-gradient-to-br from-blue-50 to-indigo-100 p-5 flex flex-col justify-between overflow-hidden">
<div className="flex flex-col items-center justify-center space-y-5">
<div className="text-center">
<div className="w-20 h-20 rounded-2xl flex items-center justify-center mb-4 mx-auto shadow-lg overflow-hidden">
<div className="w-20 h-20 rounded-2xl flex items-center justify-center mb-3 mx-auto shadow-lg overflow-hidden">
<img
src="icons/icon128.png"
alt="BrowsePing Logo"
Expand All @@ -17,7 +17,7 @@ const WelcomeScreen: React.FC = () => {
<p className="text-lg text-gray-600">Connect with friends across the web</p>
</div>

<div className="w-full max-w-xs space-y-4">
<div className="w-full max-w-xs space-y-3">
<a
href="#/email-verification"
className="block w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200 text-center"
Expand All @@ -31,14 +31,21 @@ const WelcomeScreen: React.FC = () => {
>
Log In
</a>

<a
href="#/onboarding"
className="block w-full bg-transparent hover:bg-blue-50 text-blue-600 font-medium py-3 px-4 border border-blue-300 rounded-lg transition duration-200 text-center"
>
Explore Again
</a>
</div>

<p className="text-sm text-gray-500 max-w-xs text-center mt-8">
<p className="text-sm text-gray-500 max-w-xs text-center">
Join BrowsePing to see what your friends are browsing in real-time, with full privacy controls.
</p>
</div>

<div className="mt-8 pt-4 border-t border-gray-200">
<div className="pt-3 border-t border-gray-200">
<div className="flex justify-center space-x-4">
<div className="text-center">
<div className="bg-blue-100 rounded-full p-2 mb-2 inline-block">
Expand Down
3 changes: 2 additions & 1 deletion src/popup/context/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { createContext, useState, useContext, useEffect } from 'react';
import { User, updateUserInLocalStorage, getUserFromLocalStorage, clearUserFromLocalStorage } from '../utils/localStorage';
import { User, updateUserInLocalStorage, getUserFromLocalStorage, clearUserFromLocalStorage, resetOnboardingStatus } from '../utils/localStorage';
import { logout as logoutAPI } from '../../services/api';
import toast from 'react-hot-toast';

Expand Down Expand Up @@ -67,6 +67,7 @@ export const AuthProvider: React.FC<{children: React.ReactNode}> = ({ children }
const handleLogoutLocal = async () => {
setUser(null);
await clearUserFromLocalStorage();
await resetOnboardingStatus();
chrome.runtime.sendMessage({ type: 'LOGOUT' });
};

Expand Down
16 changes: 14 additions & 2 deletions src/popup/pages/MorePage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from 'react';
import { FiUser, FiBell, FiHelpCircle, FiInfo, FiLogOut, FiShield, FiMail } from 'react-icons/fi';
import { FiUser, FiBell, FiHelpCircle, FiInfo, FiLogOut, FiShield, FiMail, FiCompass } from 'react-icons/fi';
import { useAuth } from '../context/AuthContext';
import toast from 'react-hot-toast';
import { resetOnboardingStatus } from '../utils/localStorage';

interface MenuItem {
icon: React.ReactNode;
Expand All @@ -20,6 +21,11 @@ const MorePage: React.FC = () => {
window.location.href = '#/';
};

const handleViewTutorial = async () => {
await resetOnboardingStatus();
window.location.href = '#/onboarding';
};

const menuItems: MenuItem[] = [
// Account Section
{
Expand All @@ -37,7 +43,13 @@ const MorePage: React.FC = () => {
category: 'account'
},

// Preferences Section
{
icon: <FiCompass size={20} />,
label: 'View Tutorial',
description: 'Learn how to use BrowsePing features',
action: handleViewTutorial,
category: 'preferences'
},
{
icon: <FiBell size={20} />,
label: 'Notifications',
Expand Down
18 changes: 15 additions & 3 deletions src/popup/router/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import AboutPage from '../pages/AboutPage';
import HelpPage from '../pages/HelpPage';
import PrivacyPage from '../pages/PrivacyPage';
import { MessageProvider } from '../context/MessageContext';
import { OnboardingFlow } from '../components/onboarding';
import { getOnboardingStatus } from '../utils/localStorage';

const AppRouter: React.FC = () => {
const { user, loading } = useAuth();
Expand All @@ -50,16 +52,15 @@ const AppRouter: React.FC = () => {
const forgotPasswordStep = localStorage.getItem('forgotPasswordStep');
const currentHash = window.location.hash;

// Handle email verification flow
if (pendingEmail && (currentHash === '#/' || currentHash === '' || currentHash === '#')) {
setTimeout(() => {
if (window.location.hash === '#/' || window.location.hash === '' || window.location.hash === '#') {
window.location.hash = '#/email-verification';
}
}, 200);
return;
}

// Handle forgot password flow restoration
if (forgotPasswordStep && (currentHash === '#/' || currentHash === '' || currentHash === '#')) {
setTimeout(() => {
if (window.location.hash === '#/' || window.location.hash === '' || window.location.hash === '#') {
Expand All @@ -76,6 +77,15 @@ const AppRouter: React.FC = () => {
}
}
}, 200);
return;
}

if (currentHash === '#/' || currentHash === '' || currentHash === '#') {
setTimeout(() => {
if (window.location.hash === '#/' || window.location.hash === '' || window.location.hash === '#') {
window.location.hash = '#/onboarding';
}
}, 200);
}
}
}, [user, loading]);
Expand All @@ -94,7 +104,9 @@ const AppRouter: React.FC = () => {
{/* Auth Routes */}
{!user && (
<>
<Route path="/" element={<WelcomeScreen />} />
<Route path="/" element={<OnboardingFlow />} />
<Route path="/onboarding" element={<OnboardingFlow />} />
<Route path="/welcome" element={<WelcomeScreen />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/email-verification" element={<EmailVerificationPage onVerificationSuccess={handleEmailVerified}/>} />
<Route path="/signup" element={<SignupPage verifiedEmail={verifiedEmail} />} />
Expand Down
38 changes: 35 additions & 3 deletions src/popup/utils/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,6 @@ export const getUserFromLocalStorage = (): Promise<User | null> => {
});
};

/**
* Clears user data from localStorage
*/
export const clearUserFromLocalStorage = (): Promise<void> => {
return new Promise((resolve) => {
chrome.storage.local.remove('user', () => {
Expand All @@ -130,3 +127,38 @@ export const clearUserFromLocalStorage = (): Promise<void> => {
});
});
};

export const getOnboardingStatus = (): Promise<boolean> => {
return new Promise((resolve) => {
chrome.storage.local.get('hasCompletedOnboarding', (result) => {
if (chrome.runtime.lastError) {
console.error('Error reading onboarding status:', chrome.runtime.lastError);
resolve(false);
return;
}
resolve(result.hasCompletedOnboarding || false);
});
});
};

export const setOnboardingComplete = (): Promise<void> => {
return new Promise((resolve) => {
chrome.storage.local.set({ hasCompletedOnboarding: true }, () => {
if (chrome.runtime.lastError) {
console.error('Error setting onboarding status:', chrome.runtime.lastError);
}
resolve();
});
});
};

export const resetOnboardingStatus = (): Promise<void> => {
return new Promise((resolve) => {
chrome.storage.local.set({ hasCompletedOnboarding: false }, () => {
if (chrome.runtime.lastError) {
console.error('Error resetting onboarding status:', chrome.runtime.lastError);
}
resolve();
});
});
};