import { AppLayout, type SidebarItem } from "@/components/AppLayout"; import { useDisclosure } from "@mantine/hooks"; import { Home, Layers, Loader2, // MapPin, Package, Receipt, Settings, } from "lucide-react"; import { useEffect, useRef } from "react"; import { Navigate, Outlet, Route, Routes, useLocation, useNavigate, } from "react-router-dom"; import OnboardingResumeBanner, { AccountReviewBanner, } from "./components/onboarding/OnboardingResumeBanner"; import OnboardingWizardDialog from "./components/onboarding/OnboardingWizardDialog"; import useAuth from "./hooks/useAuth"; import { startTokenRefreshScheduler, stopTokenRefreshScheduler, } from "./utils/refreshScheduler"; import EDRFreightLandingPage from "./pages/EDRFreightLandingPage"; import MyPortalPage from "./pages/MyPortalPage"; import MySignaturePage from "./pages/MySignaturePage"; import SettingsPage from "./pages/SettingsPage"; import ForgotPasswordPage from "./pages/accounts/ForgotPasswordPage"; import LoginPage from "./pages/accounts/LoginPage"; import SetPasswordPage from "./pages/accounts/SetPasswordPage"; import SignupPage from "./pages/accounts/SignupPage"; import VerificationOtpPage from "./pages/accounts/VerificationOtpPage"; import InvoiceDetailPage from "./pages/billing/InvoiceDetailPage"; import InvoicesList from "./pages/billing/InvoicesList"; import BookingContractPage from "./pages/bookings/BookingContractPage"; import BookingDetailPage from "./pages/bookings/BookingDetailPage"; import BookingsListPage from "./pages/bookings/BookingsListPage"; import EditBookingPage from "./pages/bookings/EditBookingPage"; import ContractClearanceFlow from "./pages/contracts/ContractClearanceFlow"; import ContractDetailPage from "./pages/contracts/ContractDetailPage"; import ContractViewPage from "./pages/contracts/ContractViewPage"; import ContractsList from "./pages/contracts/ContractsList"; import NewContractPage from "./pages/contracts/NewContractPage"; import NewShipmentPage from "./pages/contracts/NewShipmentPage"; import NewShipmentRequestPage from "./pages/contracts/NewShipmentRequestPage"; import CheckPaymentPage from "./pages/payments/CheckPaymentPage"; import PaymentFailurePage from "./pages/payments/PaymentFailurePage"; import PaymentSuccessPage from "./pages/payments/PaymentSuccessPage"; import TrackingPage from "./pages/tracking/TrackingPage"; function FullScreenSpinner() { return (
); } function LogoutHandler() { const { logout } = useAuth(); const navigate = useNavigate(); const hasRun = useRef(false); useEffect(() => { if (hasRun.current) return; hasRun.current = true; logout().then(() => navigate("/login", { replace: true })); }, []); return ; } /** Blocks unauthenticated users; renders children only with a valid session. */ function RequireAuth() { const { isPending, isAuthenticated } = useAuth(); const location = useLocation(); if (isPending) return ; if (!isAuthenticated) return ; return ; } /** * Waits for the company query so downstream routes can rely on it being * resolved. Onboarding is enforced by OnboardingGate, not here. */ function RequireCompany() { const { customerQuery } = useAuth(); if (customerQuery.isPending) return ; return ; } /** * Routes an un-onboarded user may still visit. The wizard auto-opens but is * dismissable, so they can browse these freely; any other route forces the * wizard back open and bounces them home. */ const ONBOARDING_ALLOWED_PATHS = ["/portal", "/signature"]; function isOnboardingAllowedPath(pathname: string): boolean { const path = pathname.toLowerCase(); return ONBOARDING_ALLOWED_PATHS.some( (p) => path === p || path.startsWith(p + "/"), ); } /** * Enforces first-run onboarding. The home (dashboard) and signature pages stay * reachable while onboarding is incomplete; the wizard auto-opens on login but * can be dismissed to use those pages. Visiting any other page bounces back to * home and re-opens the wizard. New users (no company yet) are treated the same * as users who haven't completed onboarding. */ function OnboardingGate() { const { company, onboardingCompleted } = useAuth(); const location = useLocation(); const needsOnboarding = !company || !onboardingCompleted; const allowedHere = isOnboardingAllowedPath(location.pathname); // Open by default while onboarding is pending (covers the login case). const [wizardOpen, { open: openWizard, close: closeWizard }] = useDisclosure(false); // Re-evaluate on every navigation: force the wizard open on blocked routes, // and auto-open on first arrival while onboarding is pending. useEffect(() => { if (needsOnboarding && !allowedHere) { openWizard(); } }, [needsOnboarding, allowedHere, location.pathname, openWizard]); // Auto-open once when onboarding becomes/loads as pending (login). const autoOpenedRef = useRef(false); useEffect(() => { if (needsOnboarding && !autoOpenedRef.current) { autoOpenedRef.current = true; openWizard(); } if (!needsOnboarding) autoOpenedRef.current = false; }, [needsOnboarding, openWizard]); if (needsOnboarding && !allowedHere) { return ; } return ( <> {needsOnboarding && } {!needsOnboarding && } ); } /** Keeps authenticated users off the login/signup pages. */ function RedirectIfAuthed() { const { isPending, isAuthenticated } = useAuth(); if (isPending) return ; if (isAuthenticated) return ; return ; } /** Landing page for visitors; authenticated users go straight to the portal. */ function LandingRoute() { const { isPending, isAuthenticated } = useAuth(); if (isPending) return ; if (isAuthenticated) return ; return ; } const sidebarItems: SidebarItem[] = [ { label: "Home", href: "/portal", icon: }, { label: "Contracts", href: "/contracts", icon: , }, { label: "Bookings", href: "/bookings", icon: , }, // { // label: "Tracking", // href: "/tracking", // icon: , // }, { label: "Invoices", href: "/billing", icon: , }, { section: "Account", label: "Settings", href: "/settings", icon: , }, ]; const App = () => { const navigate = useNavigate(); const location = useLocation(); const { user, company, companyType, createProfile, isAuthenticated } = useAuth(); // Keep the server session alive while a user is logged in. Runs after // login, signup, and page-reload bootstrap alike. useEffect(() => { if (!isAuthenticated) { stopTokenRefreshScheduler(); return; } startTokenRefreshScheduler(); return stopTokenRefreshScheduler; }, [isAuthenticated]); const displayName = user?.name?.en || user?.username || user?.email || "User"; const userEmail = user?.email; const companyProfiles = company?.company?.companyProfiles ?? []; return ( {/* Public routes */} } /> } /> } /> {/* Payment provider browser redirects (PAYMENT_RETURN_URL / PAYMENT_FAILURE_URL) */} } /> } /> {/* Auth pages — inaccessible once logged in */} }> } /> } /> } /> {/* Signup-flow pages; reached while a session already exists */} } /> } /> }> }> } > } /> {/* Bookings are created against a contract, but the full list is browsable here. New-booking entry still routes via a contract. */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* Completion of an initiated (bare) booking after per-booking clearance — same form, submits to the complete endpoint. */} } /> } /> } /> } /> } /> } /> } /> {/* Profile was merged into Settings — keep old links working. */} } /> } /> } /> } /> ); }; export default App;