Shaheer Sarfaraz 5ed74bb59c
Tracer links (#174)
* 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
2026-02-18 22:05:15 +00:00

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 />
</>
);
};