diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 35d47f8f..1f6b31f4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,5 @@ -import { Routes, Route, Navigate } from 'react-router-dom'; -import { useEffect } from 'react'; +import { Routes, Route, Navigate, useNavigate } from 'react-router-dom'; +import { useEffect, useState } from 'react'; import Home from './pages/Home'; import Debugger from './pages/Debugger'; import PayrollScheduler from './pages/PayrollScheduler'; @@ -24,9 +24,12 @@ import Login from './pages/Login'; import AuthCallback from './pages/AuthCallback'; import { useTranslation } from 'react-i18next'; import { contractService } from './services/contracts'; +import { OnboardingTour, ONBOARDING_KEY } from './components/OnboardingTour'; function App() { const { t } = useTranslation(); + const navigate = useNavigate(); + const [showTour, setShowTour] = useState(false); // Initialize contract service on app startup useEffect(() => { @@ -35,8 +38,25 @@ function App() { }); }, []); + // Auto-start onboarding tour on first authenticated visit + useEffect(() => { + const tourCompleted = localStorage.getItem(ONBOARDING_KEY) === 'true'; + const hasAuth = localStorage.getItem('payd_auth_token') !== null; + if (!tourCompleted && hasAuth) { + setShowTour(true); + } + }, []); + + // Listen for "restart tour" requests from e.g. Help Center + useEffect(() => { + const handleRestartTour = () => setShowTour(true); + window.addEventListener('payd:restart-tour', handleRestartTour); + return () => window.removeEventListener('payd:restart-tour', handleRestartTour); + }, []); + return ( - + <> + }> } /> } /> + setShowTour(false)} + /> + ); } diff --git a/frontend/src/components/DashboardSidebar.tsx b/frontend/src/components/DashboardSidebar.tsx index 7767a245..c1e44df0 100644 --- a/frontend/src/components/DashboardSidebar.tsx +++ b/frontend/src/components/DashboardSidebar.tsx @@ -19,12 +19,13 @@ interface NavItem { icon: React.ComponentType<{ className?: string }>; label: string; path: string; + tourId?: string; } const navItems: NavItem[] = [ { icon: LayoutDashboard, label: 'Dashboard', path: '/' }, - { icon: Wallet, label: 'Payroll', path: '/payroll' }, - { icon: Users, label: 'Employees', path: '/employee' }, + { icon: Wallet, label: 'Payroll', path: '/payroll', tourId: 'tour-payroll' }, + { icon: Users, label: 'Employees', path: '/employee', tourId: 'tour-employees' }, { icon: TrendingUp, label: 'Forecast', path: '/forecast' }, { icon: FileText, label: 'Reports', path: '/reports' }, { icon: Receipt, label: 'Tax Compliance', path: '/tax-compliance' }, @@ -58,6 +59,7 @@ export const DashboardSidebar: React.FC = ({ `flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200 group ${ diff --git a/frontend/src/components/OnboardingTour.tsx b/frontend/src/components/OnboardingTour.tsx index 5d6804e2..89ab9b2f 100644 --- a/frontend/src/components/OnboardingTour.tsx +++ b/frontend/src/components/OnboardingTour.tsx @@ -1,62 +1,97 @@ -import React from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import Joyride, { Step, CallBackProps, STATUS } from 'react-joyride'; +const ONBOARDING_KEY = 'payd_onboarding_complete'; + const TOUR_STEPS: Step[] = [ { target: '#tour-welcome', - content: "Welcome to PayD! Let's take a quick tour to get you started.", + content: 'Welcome to PayD! This quick tour will show you how to manage your workforce and payroll — all in just a few clicks.', placement: 'bottom', disableBeacon: true, }, { target: '#tour-connect', - content: 'First, connect your Stellar wallet to securely manage your organization.', + content: 'Connect your Stellar wallet to securely manage your organization and process payments.', placement: 'bottom', }, { target: '#tour-employees', - content: 'Navigate here to set up your organization and manage your workforce.', - placement: 'bottom', + content: 'Manage your team here. Navigate to the Employees page to add and manage your workforce.', + placement: 'right', }, { target: '#tour-add-employee', - content: 'Add your employees here to include them in the payroll.', + content: 'Click here to add your first employee. Fill in their details and save them to the payroll roster.', placement: 'right', }, { target: '#tour-payroll', - content: 'Once your team is set up, head over to the Payroll Scheduler.', - placement: 'bottom', + content: 'Set up automated payroll streams here. Configure recurring payments for your team in real-time.', + placement: 'right', }, { target: '#tour-init-payroll', - content: - 'Set up automated streams and fund your distribution account to pay your team in real-time.', - placement: 'top', + content: 'Open the scheduling wizard to configure recurring payments, set frequency, and fund your distribution account.', + placement: 'bottom', }, ]; export const OnboardingTour: React.FC<{ run: boolean; + navigateTo?: (path: string) => void; onComplete: () => void; -}> = ({ run, onComplete }) => { - const handleJoyrideCallback = (data: CallBackProps) => { - const { status } = data; - const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; +}> = ({ run, navigateTo, onComplete }) => { + const [stepIndex, setStepIndex] = useState(0); + const [internalRun, setInternalRun] = useState(false); - if (finishedStatuses.includes(status)) { - onComplete(); + useEffect(() => { + if (run) { + setStepIndex(0); + setInternalRun(true); + } else { + setInternalRun(false); } - }; + }, [run]); + + const handleJoyrideCallback = useCallback( + (data: CallBackProps) => { + const { index, type, status } = data; + + const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; + if (finishedStatuses.includes(status)) { + localStorage.setItem(ONBOARDING_KEY, 'true'); + setInternalRun(false); + onComplete(); + return; + } + + // Auto-navigate to correct page when transitioning between steps + if (type === 'step:after') { + if (index === 2 && navigateTo) { + // After step 3 (Employees), navigate to employee page for step 4 (Add Employee) + navigateTo('/employee'); + } + if (index === 4 && navigateTo) { + // After step 5 (Payroll sidebar), navigate to payroll page for step 6 (Init Payroll) + navigateTo('/payroll'); + } + setStepIndex(index + 1); + } + }, + [navigateTo, onComplete] + ); return ( ); }; + +export { ONBOARDING_KEY }; \ No newline at end of file diff --git a/frontend/src/pages/HelpCenter.tsx b/frontend/src/pages/HelpCenter.tsx index 60844816..4a379925 100644 --- a/frontend/src/pages/HelpCenter.tsx +++ b/frontend/src/pages/HelpCenter.tsx @@ -137,6 +137,27 @@ export default function HelpCenter() { ))} + + {/* Restart Onboarding Tour */} +
+
+

+ Get a Guided Tour +

+

+ Replay the interactive onboarding tour to learn how to connect a wallet, add your employees, and set up your first payroll. +

+
+ +
); diff --git a/frontend/src/pages/PayrollScheduler.tsx b/frontend/src/pages/PayrollScheduler.tsx index ee67b889..f243aa3f 100644 --- a/frontend/src/pages/PayrollScheduler.tsx +++ b/frontend/src/pages/PayrollScheduler.tsx @@ -331,6 +331,7 @@ export default function PayrollScheduler() {