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;