import React, { useState, useEffect, useRef } from 'react'; import { Outlet, useLocation, useSearchParams, useNavigate } from 'react-router-dom'; import Sidebar from '../components/Sidebar'; import TopBar from '../components/TopBar'; import TrialBanner from '../components/TrialBanner'; import { Business, User } from '../types'; import MasqueradeBanner from '../components/MasqueradeBanner'; import OnboardingWizard from '../components/OnboardingWizard'; import { useStopMasquerade } from '../hooks/useAuth'; import { MasqueradeStackEntry } from '../api/auth'; import { useScrollToTop } from '../hooks/useScrollToTop'; interface BusinessLayoutProps { business: Business; user: User; darkMode: boolean; toggleTheme: () => void; onSignOut: () => void; updateBusiness: (updates: Partial) => void; } const BusinessLayout: React.FC = ({ business, user, darkMode, toggleTheme, onSignOut, updateBusiness }) => { const [isCollapsed, setIsCollapsed] = useState(false); const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [showOnboarding, setShowOnboarding] = useState(false); const mainContentRef = useRef(null); const location = useLocation(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); useScrollToTop(); // Check for trial expiration and redirect useEffect(() => { // Don't check if already on trial-expired page if (location.pathname === '/trial-expired') { return; } // Redirect to trial-expired page if trial has expired if (business.isTrialExpired && business.status === 'Trial') { navigate('/trial-expired', { replace: true }); } }, [business.isTrialExpired, business.status, location.pathname, navigate]); // Masquerade logic - now using the stack system const [masqueradeStack, setMasqueradeStack] = useState([]); const stopMasqueradeMutation = useStopMasquerade(); useEffect(() => { const stackJson = localStorage.getItem('masquerade_stack'); if (stackJson) { try { setMasqueradeStack(JSON.parse(stackJson)); } catch (e) { console.error('Failed to parse masquerade stack data', e); } } }, []); const handleStopMasquerade = () => { stopMasqueradeMutation.mutate(); }; // Get the previous user from the stack (the one we'll return to) const previousUser = masqueradeStack.length > 0 ? { id: masqueradeStack[masqueradeStack.length - 1].user_id, username: masqueradeStack[masqueradeStack.length - 1].username, name: masqueradeStack[masqueradeStack.length - 1].username, role: masqueradeStack[masqueradeStack.length - 1].role, email: '', is_staff: false, is_superuser: false, } as User : null; // Get the original user (first in the stack) const originalUser = masqueradeStack.length > 0 ? { id: masqueradeStack[0].user_id, username: masqueradeStack[0].username, name: masqueradeStack[0].username, role: masqueradeStack[0].role, email: '', is_staff: false, is_superuser: false, } as User : null; useEffect(() => { mainContentRef.current?.focus(); setIsMobileMenuOpen(false); }, [location.pathname]); // Check if returning from Stripe Connect onboarding useEffect(() => { const isOnboardingReturn = searchParams.get('onboarding') === 'true'; // Only show onboarding if returning from Stripe Connect if (isOnboardingReturn) { setShowOnboarding(true); } }, [searchParams]); const handleOnboardingComplete = () => { setShowOnboarding(false); // Update local state immediately so wizard doesn't re-appear updateBusiness({ initialSetupComplete: true }); }; const handleOnboardingSkip = () => { setShowOnboarding(false); // If they skip Stripe setup, disable payments updateBusiness({ paymentsEnabled: false }); }; return (
{ }} />
{isMobileMenuOpen &&
setIsMobileMenuOpen(false)}>
}
setIsCollapsed(!isCollapsed)} />
{originalUser && ( )} {/* Show trial banner if trial is active and payments not yet enabled */} {business.isTrialActive && !business.paymentsEnabled && business.plan !== 'Free' && ( )} setIsMobileMenuOpen(true)} />
{/* Pass all necessary context down to child routes */}
{/* Onboarding wizard for paid-tier businesses */} {showOnboarding && ( )}
); }; export default BusinessLayout;