* initial commit * format links right jobops.dakheera47.com/cv/shaheer-google-de * don't support legacy * remove phishing look * smaller links * readiness check in settings * rework UX * right col * pop a modal * modal improvements * show links * documentation disclaimer * fix(tracer-links): preserve descriptive resume link labels * fix(tracer-links): classify bot user agents before browser families * fix(tracer-links): reject non-http redirect destinations * fix(tracer-redirect): disable caching for tracked redirects * fix(origin): prefer canonical public base url over forwarded headers * fix(auth): protect tracer analytics routes behind basic auth * fix(ui): rename misleading tracer drilldown human metric * style(tests): format tracer-links invalid-destination assertion * fix(tests): prevent mocked fs from breaking sqlite data-dir resolution * style(docs): format versioned docs json for biome * fix(tests): mock tracer-links in pdf skills validation suite
128 lines
4.8 KiB
TypeScript
128 lines
4.8 KiB
TypeScript
/**
|
|
* Main App component.
|
|
*/
|
|
|
|
import React, { useRef } from "react";
|
|
import { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
|
import { CSSTransition, SwitchTransition } from "react-transition-group";
|
|
|
|
import { Toaster } from "@/components/ui/sonner";
|
|
import { trackEvent } from "@/lib/analytics";
|
|
import { BasicAuthPrompt } from "./components/BasicAuthPrompt";
|
|
import { OnboardingGate } from "./components/OnboardingGate";
|
|
import { useDemoInfo } from "./hooks/useDemoInfo";
|
|
import { GmailOauthCallbackPage } from "./pages/GmailOauthCallbackPage";
|
|
import { HomePage } from "./pages/HomePage";
|
|
import { InProgressBoardPage } from "./pages/InProgressBoardPage";
|
|
import { JobPage } from "./pages/JobPage";
|
|
import { OrchestratorPage } from "./pages/OrchestratorPage";
|
|
import { SettingsPage } from "./pages/SettingsPage";
|
|
import { TracerLinksPage } from "./pages/TracerLinksPage";
|
|
import { TrackingInboxPage } from "./pages/TrackingInboxPage";
|
|
import { VisaSponsorsPage } from "./pages/VisaSponsorsPage";
|
|
|
|
/** Backwards-compatibility redirects: old URL paths -> new URL paths */
|
|
const REDIRECTS: Array<{ from: string; to: string }> = [
|
|
{ from: "/", to: "/jobs/ready" },
|
|
{ from: "/home", to: "/overview" },
|
|
{ from: "/ready", to: "/jobs/ready" },
|
|
{ from: "/ready/:jobId", to: "/jobs/ready/:jobId" },
|
|
{ from: "/discovered", to: "/jobs/discovered" },
|
|
{ from: "/discovered/:jobId", to: "/jobs/discovered/:jobId" },
|
|
{ from: "/applied", to: "/jobs/applied" },
|
|
{ from: "/applied/:jobId", to: "/jobs/applied/:jobId" },
|
|
{ from: "/in-progress", to: "/applications/in-progress" },
|
|
{ from: "/in-progress/:jobId", to: "/applications/in-progress" },
|
|
{ from: "/jobs/in_progress", to: "/applications/in-progress" },
|
|
{ from: "/jobs/in_progress/:jobId", to: "/applications/in-progress" },
|
|
{ from: "/all", to: "/jobs/all" },
|
|
{ from: "/all/:jobId", to: "/jobs/all/:jobId" },
|
|
];
|
|
|
|
export const App: React.FC = () => {
|
|
const location = useLocation();
|
|
const nodeRef = useRef<HTMLDivElement>(null);
|
|
const demoInfo = useDemoInfo();
|
|
|
|
// Determine a stable key for transitions to avoid unnecessary unmounts when switching sub-tabs
|
|
const pageKey = React.useMemo(() => {
|
|
const firstSegment = location.pathname.split("/")[1] || "jobs";
|
|
if (firstSegment === "jobs") {
|
|
return "orchestrator";
|
|
}
|
|
return firstSegment;
|
|
}, [location.pathname]);
|
|
|
|
return (
|
|
<>
|
|
<OnboardingGate />
|
|
<BasicAuthPrompt />
|
|
{demoInfo?.demoMode && (
|
|
<div className="w-full border-b border-amber-400/50 bg-amber-500/20 px-4 py-2 text-center text-xs text-amber-100 backdrop-blur">
|
|
Demo mode: integrations are simulated and data resets every{" "}
|
|
{demoInfo.resetCadenceHours} hours.{" "}
|
|
<a
|
|
className="font-semibold underline underline-offset-2 hover:text-amber-50"
|
|
href="https://github.com/DaKheera47/job-ops"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
onClick={() =>
|
|
trackEvent("star_repo_click", { location: "demo_mode_banner" })
|
|
}
|
|
>
|
|
Star the repo on GitHub
|
|
</a>
|
|
.
|
|
</div>
|
|
)}
|
|
<div>
|
|
<SwitchTransition mode="out-in">
|
|
<CSSTransition
|
|
key={pageKey}
|
|
nodeRef={nodeRef}
|
|
timeout={100}
|
|
classNames="page"
|
|
unmountOnExit
|
|
>
|
|
<div ref={nodeRef}>
|
|
<Routes location={location}>
|
|
{/* Backwards-compatibility redirects */}
|
|
{REDIRECTS.map(({ from, to }) => (
|
|
<Route
|
|
key={from}
|
|
path={from}
|
|
element={<Navigate to={to} replace />}
|
|
/>
|
|
))}
|
|
|
|
{/* Application routes */}
|
|
<Route path="/overview" element={<HomePage />} />
|
|
<Route
|
|
path="/oauth/gmail/callback"
|
|
element={<GmailOauthCallbackPage />}
|
|
/>
|
|
<Route path="/job/:id" element={<JobPage />} />
|
|
<Route
|
|
path="/applications/in-progress"
|
|
element={<InProgressBoardPage />}
|
|
/>
|
|
<Route path="/settings" element={<SettingsPage />} />
|
|
<Route path="/tracer-links" element={<TracerLinksPage />} />
|
|
<Route path="/visa-sponsors" element={<VisaSponsorsPage />} />
|
|
<Route path="/tracking-inbox" element={<TrackingInboxPage />} />
|
|
<Route path="/jobs/:tab" element={<OrchestratorPage />} />
|
|
<Route
|
|
path="/jobs/:tab/:jobId"
|
|
element={<OrchestratorPage />}
|
|
/>
|
|
</Routes>
|
|
</div>
|
|
</CSSTransition>
|
|
</SwitchTransition>
|
|
</div>
|
|
|
|
<Toaster position="bottom-right" richColors closeButton />
|
|
</>
|
|
);
|
|
};
|