{/* Sticky header with section title and small close button */}
((_, ref) => {
const { t } = useTranslation();
@@ -179,7 +180,7 @@ const QuickAccessBar = forwardRef
((_, ref) => {
size: 'lg',
type: 'action',
onClick: () => {
- startTour();
+ // This will be overridden by the wrapper logic
},
},
{
@@ -258,11 +259,70 @@ const QuickAccessBar = forwardRef((_, ref) => {
{/* Bottom section */}
- {bottomButtons.map((config, index) => (
-
- {renderNavButton(config, index)}
-
- ))}
+ {bottomButtons.map((buttonConfig, index) => {
+ // Handle help button with menu or direct action
+ if (buttonConfig.id === 'help') {
+ const isAdmin = config?.isAdmin === true;
+
+ // If not admin, just show button that starts tools tour directly
+ if (!isAdmin) {
+ return (
+ startTour('tools')}
+ >
+ {renderNavButton(buttonConfig, index)}
+
+ );
+ }
+
+ // If admin, show menu with both options
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ {renderNavButton(buttonConfig, index)}
+
+ );
+ })}
diff --git a/frontend/src/core/contexts/AdminTourOrchestrationContext.tsx b/frontend/src/core/contexts/AdminTourOrchestrationContext.tsx
new file mode 100644
index 000000000..3bac8f101
--- /dev/null
+++ b/frontend/src/core/contexts/AdminTourOrchestrationContext.tsx
@@ -0,0 +1,124 @@
+import React, { createContext, useContext, useCallback, useRef } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+
+interface AdminTourOrchestrationContextType {
+ // State management
+ saveAdminState: () => void;
+ restoreAdminState: () => void;
+
+ // Modal & navigation
+ openConfigModal: () => void;
+ closeConfigModal: () => void;
+ navigateToSection: (section: string) => void;
+ scrollNavToSection: (section: string) => void;
+
+ // Section-specific actions
+ scrollToSetting: (settingId: string) => void;
+}
+
+const AdminTourOrchestrationContext = createContext
(undefined);
+
+export const AdminTourOrchestrationProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ // Store the user's location before tour starts
+ const savedLocationRef = useRef('');
+
+ const saveAdminState = useCallback(() => {
+ savedLocationRef.current = location.pathname;
+ console.log('Saving admin state, location:', location.pathname);
+ }, [location.pathname]);
+
+ const restoreAdminState = useCallback(() => {
+ console.log('Restoring admin state, saved location:', savedLocationRef.current);
+
+ // Navigate back to saved location or home
+ const targetPath = savedLocationRef.current || '/';
+ navigate(targetPath, { replace: true });
+
+ savedLocationRef.current = '';
+ }, [navigate]);
+
+ const openConfigModal = useCallback(() => {
+ // Navigate to settings overview to open the modal
+ navigate('/settings/overview');
+ }, [navigate]);
+
+ const closeConfigModal = useCallback(() => {
+ // Navigate back to home to close the modal
+ navigate('/', { replace: true });
+ }, [navigate]);
+
+ const navigateToSection = useCallback((section: string) => {
+ navigate(`/settings/${section}`);
+ }, [navigate]);
+
+ const scrollNavToSection = useCallback((section: string): Promise => {
+ return new Promise((resolve) => {
+ const navElement = document.querySelector(`[data-tour="admin-${section}-nav"]`) as HTMLElement;
+ const scrollContainer = document.querySelector('.modal-nav-scroll') as HTMLElement;
+
+ if (navElement && scrollContainer) {
+ // Get the position of the nav element relative to the scroll container
+ const navTop = navElement.offsetTop;
+ const containerHeight = scrollContainer.clientHeight;
+ const navHeight = navElement.offsetHeight;
+
+ // Calculate scroll position to center the element
+ const scrollTo = navTop - (containerHeight / 2) + (navHeight / 2);
+
+ // Instant scroll to avoid timing issues
+ scrollContainer.scrollTo({
+ top: Math.max(0, scrollTo),
+ behavior: 'auto'
+ });
+
+ // Use multiple animation frames to ensure browser has fully updated
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ resolve();
+ });
+ });
+ });
+ } else {
+ resolve();
+ }
+ });
+ }, []);
+
+ const scrollToSetting = useCallback((settingId: string) => {
+ // Wait for the DOM to update, then scroll to the setting
+ setTimeout(() => {
+ const element = document.querySelector(`[data-tour="${settingId}"]`);
+ if (element) {
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+ }, 300);
+ }, []);
+
+ const value: AdminTourOrchestrationContextType = {
+ saveAdminState,
+ restoreAdminState,
+ openConfigModal,
+ closeConfigModal,
+ navigateToSection,
+ scrollNavToSection,
+ scrollToSetting,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useAdminTourOrchestration = (): AdminTourOrchestrationContextType => {
+ const context = useContext(AdminTourOrchestrationContext);
+ if (!context) {
+ throw new Error('useAdminTourOrchestration must be used within AdminTourOrchestrationProvider');
+ }
+ return context;
+};
diff --git a/frontend/src/core/contexts/OnboardingContext.tsx b/frontend/src/core/contexts/OnboardingContext.tsx
index 21bdadc4d..817f3dd5a 100644
--- a/frontend/src/core/contexts/OnboardingContext.tsx
+++ b/frontend/src/core/contexts/OnboardingContext.tsx
@@ -2,14 +2,17 @@ import React, { createContext, useContext, useState, useEffect, useCallback } fr
import { usePreferences } from '@app/contexts/PreferencesContext';
import { useShouldShowWelcomeModal } from '@app/hooks/useShouldShowWelcomeModal';
+export type TourType = 'tools' | 'admin';
+
interface OnboardingContextValue {
isOpen: boolean;
currentStep: number;
+ tourType: TourType;
setCurrentStep: (step: number) => void;
- startTour: () => void;
+ startTour: (type?: TourType) => void;
closeTour: () => void;
completeTour: () => void;
- resetTour: () => void;
+ resetTour: (type?: TourType) => void;
showWelcomeModal: boolean;
setShowWelcomeModal: (show: boolean) => void;
}
@@ -20,6 +23,7 @@ export const OnboardingProvider: React.FC<{ children: React.ReactNode }> = ({ ch
const { updatePreference } = usePreferences();
const [isOpen, setIsOpen] = useState(false);
const [currentStep, setCurrentStep] = useState(0);
+ const [tourType, setTourType] = useState('tools');
const [showWelcomeModal, setShowWelcomeModal] = useState(false);
const shouldShow = useShouldShowWelcomeModal();
@@ -30,7 +34,8 @@ export const OnboardingProvider: React.FC<{ children: React.ReactNode }> = ({ ch
}
}, [shouldShow]);
- const startTour = useCallback(() => {
+ const startTour = useCallback((type: TourType = 'tools') => {
+ setTourType(type);
setCurrentStep(0);
setIsOpen(true);
}, []);
@@ -44,8 +49,9 @@ export const OnboardingProvider: React.FC<{ children: React.ReactNode }> = ({ ch
updatePreference('hasCompletedOnboarding', true);
}, [updatePreference]);
- const resetTour = useCallback(() => {
+ const resetTour = useCallback((type: TourType = 'tools') => {
updatePreference('hasCompletedOnboarding', false);
+ setTourType(type);
setCurrentStep(0);
setIsOpen(true);
}, [updatePreference]);
@@ -55,6 +61,7 @@ export const OnboardingProvider: React.FC<{ children: React.ReactNode }> = ({ ch
value={{
isOpen,
currentStep,
+ tourType,
setCurrentStep,
startTour,
closeTour,