This commit is contained in:
root 2026-02-08 18:32:13 +00:00
parent 32fa617f32
commit 8c9a41c575
20 changed files with 97 additions and 477 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

38
dist/index.html vendored
View File

@ -1,20 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="dark"> <html lang="en" class="dark">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>QuantFortune | Long-Term Investment Strategies</title> <title>QuantFortune | Long-Term Investment Strategies</title>
<meta name="description" content="Simple, disciplined investment strategies for salaried investors. Build wealth steadily with our proven long-term approaches." /> <meta name="description" content="Simple, disciplined investment strategies for salaried investors. Build wealth steadily with our proven long-term approaches." />
<meta property="og:title" content="QuantFortune | Long-Term Investment Strategies" /> <meta property="og:title" content="QuantFortune | Long-Term Investment Strategies" />
<meta property="og:description" content="Simple, disciplined investment strategies for salaried investors." /> <meta property="og:description" content="Simple, disciplined investment strategies for salaried investors." />
<link rel="icon" type="image/png" href="/favicon.png" /> <link rel="icon" type="image/png" href="/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Architects+Daughter&family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Fira+Code:wght@300..700&family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Lora:ital,wght@0,400..700;1,400..700&family=Merriweather:ital,opsz,wght@0,18..144,300..900;1,18..144,300..900&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Outfit:wght@100..900&family=Oxanium:wght@200..800&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,100..900;1,100..900&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Architects+Daughter&family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Fira+Code:wght@300..700&family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&family=Lora:ital,wght@0,400..700;1,400..700&family=Merriweather:ital,opsz,wght@0,18..144,300..900;1,18..144,300..900&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Outfit:wght@100..900&family=Oxanium:wght@200..800&family=Playfair+Display:ital,wght@0,400..900;1,400..900&family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,100..900;1,100..900&family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&family=Space+Grotesk:wght@300..700&family=Space+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-z66hXP0q.js"></script> <script type="module" crossorigin src="/assets/index-Dko5e3BG.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-ButmEv-s.css"> <link rel="stylesheet" crossorigin href="/assets/index-Dq1pAiY6.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

View File

@ -45,7 +45,7 @@ export default function AuthDialogs({ layout = "desktop" }: AuthDialogsProps) {
}); });
useQuery<SessionUser | null>({ useQuery<SessionUser | null>({
queryKey: ["/api/me"], queryKey: ["/me"],
queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }), queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }),
}); });
@ -53,8 +53,8 @@ export default function AuthDialogs({ layout = "desktop" }: AuthDialogsProps) {
user: SessionUser, user: SessionUser,
action: "signup" | "login", action: "signup" | "login",
) => { ) => {
queryClient.setQueryData(["/api/me"], user); queryClient.setQueryData(["/me"], user);
queryClient.invalidateQueries({ queryKey: ["/api/broker/status"] }); queryClient.invalidateQueries({ queryKey: ["/broker/status"] });
toast({ toast({
title: action === "signup" ? "Account created" : "Signed in", title: action === "signup" ? "Account created" : "Signed in",
description: description:
@ -68,7 +68,7 @@ export default function AuthDialogs({ layout = "desktop" }: AuthDialogsProps) {
const signupMutation = useMutation<SessionUser, Error, AuthPayload>({ const signupMutation = useMutation<SessionUser, Error, AuthPayload>({
mutationFn: async (payload) => { mutationFn: async (payload) => {
const res = await apiRequest("POST", "/api/signup", { const res = await apiRequest("POST", "/signup", {
username: payload.email, username: payload.email,
password: payload.password, password: payload.password,
}); });
@ -85,7 +85,7 @@ export default function AuthDialogs({ layout = "desktop" }: AuthDialogsProps) {
const loginMutation = useMutation<SessionUser, Error, AuthPayload>({ const loginMutation = useMutation<SessionUser, Error, AuthPayload>({
mutationFn: async (payload) => { mutationFn: async (payload) => {
const res = await apiRequest("POST", "/api/login", payload); const res = await apiRequest("POST", "/login", payload);
return res.json(); return res.json();
}, },
onSuccess: (user) => handleAuthSuccess(user, "login"), onSuccess: (user) => handleAuthSuccess(user, "login"),
@ -109,7 +109,7 @@ export default function AuthDialogs({ layout = "desktop" }: AuthDialogsProps) {
const resetRequestMutation = useMutation({ const resetRequestMutation = useMutation({
mutationFn: async (email: string) => { mutationFn: async (email: string) => {
const res = await apiRequest("POST", "/api/password-reset/request", { email }); const res = await apiRequest("POST", "/password-reset/request", { email });
return res.json(); return res.json();
}, },
onSuccess: () => { onSuccess: () => {
@ -123,7 +123,7 @@ export default function AuthDialogs({ layout = "desktop" }: AuthDialogsProps) {
const resetConfirmMutation = useMutation({ const resetConfirmMutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
const res = await apiRequest("POST", "/api/password-reset/confirm", { const res = await apiRequest("POST", "/password-reset/confirm", {
email: resetEmail.trim(), email: resetEmail.trim(),
otp: resetOtp.trim(), otp: resetOtp.trim(),
new_password: resetPassword, new_password: resetPassword,

View File

@ -36,13 +36,13 @@ export default function AuthSection() {
}); });
const meQuery = useQuery<SessionUser | null>({ const meQuery = useQuery<SessionUser | null>({
queryKey: ["/api/me"], queryKey: ["/me"],
queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }), queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }),
}); });
const handleAuthSuccess = (user: SessionUser, action: "signup" | "login") => { const handleAuthSuccess = (user: SessionUser, action: "signup" | "login") => {
queryClient.setQueryData(["/api/me"], user); queryClient.setQueryData(["/me"], user);
queryClient.invalidateQueries({ queryKey: ["/api/broker/status"] }); queryClient.invalidateQueries({ queryKey: ["/broker/status"] });
toast({ toast({
title: action === "signup" ? "Account created" : "Signed in", title: action === "signup" ? "Account created" : "Signed in",
description: `Welcome ${user.username}`, description: `Welcome ${user.username}`,
@ -51,7 +51,7 @@ export default function AuthSection() {
const signupMutation = useMutation<SessionUser, Error, AuthPayload>({ const signupMutation = useMutation<SessionUser, Error, AuthPayload>({
mutationFn: async (payload) => { mutationFn: async (payload) => {
const res = await apiRequest("POST", "/api/signup", { const res = await apiRequest("POST", "/signup", {
username: payload.email, username: payload.email,
password: payload.password, password: payload.password,
}); });
@ -68,7 +68,7 @@ export default function AuthSection() {
const loginMutation = useMutation<SessionUser, Error, AuthPayload>({ const loginMutation = useMutation<SessionUser, Error, AuthPayload>({
mutationFn: async (payload) => { mutationFn: async (payload) => {
const res = await apiRequest("POST", "/api/login", payload); const res = await apiRequest("POST", "/login", payload);
return res.json(); return res.json();
}, },
onSuccess: (user) => handleAuthSuccess(user, "login"), onSuccess: (user) => handleAuthSuccess(user, "login"),
@ -82,11 +82,11 @@ export default function AuthSection() {
const logoutMutation = useMutation({ const logoutMutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
await apiRequest("POST", "/api/logout"); await apiRequest("POST", "/logout");
}, },
onSuccess: () => { onSuccess: () => {
queryClient.setQueryData(["/api/me"], null); queryClient.setQueryData(["/me"], null);
queryClient.removeQueries({ queryKey: ["/api/broker/status"] }); queryClient.removeQueries({ queryKey: ["/broker/status"] });
toast({ title: "Signed out" }); toast({ title: "Signed out" });
window.location.assign("/"); window.location.assign("/");
}, },

View File

@ -62,12 +62,12 @@ export default function BrokerConnectDialog({
const [holdings, setHoldings] = useState<any[]>([]); const [holdings, setHoldings] = useState<any[]>([]);
const { data: sessionUser } = useQuery<SessionUser | null>({ const { data: sessionUser } = useQuery<SessionUser | null>({
queryKey: ["/api/me"], queryKey: ["/me"],
queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }), queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }),
}); });
const { data: brokerStatus, refetch: refetchStatus } = useQuery<BrokerStatusResponse | null>({ const { data: brokerStatus, refetch: refetchStatus } = useQuery<BrokerStatusResponse | null>({
queryKey: ["/api/broker/status"], queryKey: ["/broker/status"],
queryFn: getQueryFn<BrokerStatusResponse>({ on401: "returnNull" }), queryFn: getQueryFn<BrokerStatusResponse>({ on401: "returnNull" }),
staleTime: 0, staleTime: 0,
refetchOnMount: "always", refetchOnMount: "always",
@ -82,7 +82,7 @@ export default function BrokerConnectDialog({
throw new Error("API secret is required"); throw new Error("API secret is required");
} }
const redirectUrl = `${window.location.origin}/login`; const redirectUrl = `${window.location.origin}/login`;
const res = await apiRequest("POST", "/api/broker/zerodha/login", { const res = await apiRequest("POST", "/broker/zerodha/login", {
apiKey, apiKey,
apiSecret, apiSecret,
redirectUrl, redirectUrl,
@ -102,7 +102,7 @@ export default function BrokerConnectDialog({
const holdingsMutation = useMutation({ const holdingsMutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
const res = await apiRequest("GET", "/api/zerodha/holdings"); const res = await apiRequest("GET", "/zerodha/holdings");
return res.json() as Promise<{ holdings: any[] }>; return res.json() as Promise<{ holdings: any[] }>;
}, },
onSuccess: (data) => { onSuccess: (data) => {
@ -118,7 +118,7 @@ export default function BrokerConnectDialog({
const disconnectMutation = useMutation({ const disconnectMutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
const res = await apiRequest("POST", "/api/broker/disconnect"); const res = await apiRequest("POST", "/broker/disconnect");
return res.json() as Promise<{ connected: boolean }>; return res.json() as Promise<{ connected: boolean }>;
}, },
onSuccess: () => { onSuccess: () => {

View File

@ -20,17 +20,17 @@ export default function Navigation() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { data: sessionUser } = useQuery<SessionUser | null>({ const { data: sessionUser } = useQuery<SessionUser | null>({
queryKey: ["/api/me"], queryKey: ["/me"],
queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }), queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }),
}); });
const logoutMutation = useMutation({ const logoutMutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
await apiRequest("POST", "/api/logout"); await apiRequest("POST", "/logout");
}, },
onSuccess: () => { onSuccess: () => {
queryClient.setQueryData(["/api/me"], null); queryClient.setQueryData(["/me"], null);
queryClient.removeQueries({ queryKey: ["/api/broker/status"] }); queryClient.removeQueries({ queryKey: ["/broker/status"] });
window.location.assign("/"); window.location.assign("/");
}, },
}); });

View File

@ -183,23 +183,23 @@ export default function PortfolioSection() {
isFetching: brokerStatusLoading, isFetching: brokerStatusLoading,
refetch: refetchBrokerStatus, refetch: refetchBrokerStatus,
} = useQuery<BrokerStatusResponse | null>({ } = useQuery<BrokerStatusResponse | null>({
queryKey: ["/api/broker/status"], queryKey: ["/broker/status"],
queryFn: getQueryFn<BrokerStatusResponse>({ on401: "returnNull" }), queryFn: getQueryFn<BrokerStatusResponse>({ on401: "returnNull" }),
staleTime: 0, staleTime: 0,
refetchOnMount: "always", refetchOnMount: "always",
}); });
const { data: sessionUser } = useQuery<SessionUser | null>({ const { data: sessionUser } = useQuery<SessionUser | null>({
queryKey: ["/api/me"], queryKey: ["/me"],
queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }), queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }),
}); });
const systemStatusQuery = useQuery<SystemStatusResponse>({ const systemStatusQuery = useQuery<SystemStatusResponse>({
queryKey: ["/api/system/status"], queryKey: ["/system/status"],
queryFn: getQueryFn<SystemStatusResponse>({ on401: "throw" }), queryFn: getQueryFn<SystemStatusResponse>({ on401: "throw" }),
refetchInterval: 15000, refetchInterval: 15000,
}); });
const armMutation = useMutation({ const armMutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
const res = await fetch("/api/system/arm", { const res = await fetch("/system/arm", {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
}); });
@ -211,7 +211,7 @@ export default function PortfolioSection() {
const redirect = const redirect =
payload?.detail?.redirect_url || payload?.detail?.redirect_url ||
payload?.redirect_url || payload?.redirect_url ||
"/api/broker/login"; "/broker/login";
window.location.assign(redirect); window.location.assign(redirect);
return null; return null;
} }
@ -241,9 +241,9 @@ export default function PortfolioSection() {
}); });
const holdingsQuery = useQuery<HoldingsResponse>({ const holdingsQuery = useQuery<HoldingsResponse>({
queryKey: ["/api/zerodha/holdings"], queryKey: ["/zerodha/holdings"],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", "/api/zerodha/holdings"); const res = await apiRequest("GET", "/zerodha/holdings");
return res.json(); return res.json();
}, },
enabled: !!brokerStatus?.connected, enabled: !!brokerStatus?.connected,
@ -262,9 +262,9 @@ export default function PortfolioSection() {
}); });
const fundsQuery = useQuery<FundsResponse>({ const fundsQuery = useQuery<FundsResponse>({
queryKey: ["/api/zerodha/funds"], queryKey: ["/zerodha/funds"],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", "/api/zerodha/funds"); const res = await apiRequest("GET", "/zerodha/funds");
return res.json(); return res.json();
}, },
enabled: !!brokerStatus?.connected, enabled: !!brokerStatus?.connected,
@ -325,7 +325,7 @@ export default function PortfolioSection() {
useEffect(() => { useEffect(() => {
const fetchStatus = async () => { const fetchStatus = async () => {
try { try {
const res = await fetch("/api/engine/status"); const res = await fetch("/engine/status");
const data = await res.json(); const data = await res.json();
setEngineStatus(data); setEngineStatus(data);
} catch { } catch {
@ -341,7 +341,7 @@ export default function PortfolioSection() {
useEffect(() => { useEffect(() => {
const fetchMarketStatus = async () => { const fetchMarketStatus = async () => {
try { try {
const res = await fetch("/api/market/status"); const res = await fetch("/market/status");
const data = await res.json(); const data = await res.json();
setMarketStatus(data); setMarketStatus(data);
} catch { } catch {
@ -403,11 +403,11 @@ export default function PortfolioSection() {
}, [prefersReducedMotion]); }, [prefersReducedMotion]);
const equityCurveQuery = useQuery<EquityCurveResponse>({ const equityCurveQuery = useQuery<EquityCurveResponse>({
queryKey: ["/api/zerodha/equity-curve", startDate], queryKey: ["/zerodha/equity-curve", startDate],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest( const res = await apiRequest(
"GET", "GET",
`/api/zerodha/equity-curve${startDate ? `?from=${startDate}` : ""}`, `/zerodha/equity-curve${startDate ? `?from=${startDate}` : ""}`,
); );
return res.json(); return res.json();
}, },

View File

@ -138,25 +138,25 @@ function PaperTradingPortfolio() {
const skipFirstPnlPointRef = useRef(true); const skipFirstPnlPointRef = useRef(true);
const fundsQuery = useQuery<PaperFundsResponse>({ const fundsQuery = useQuery<PaperFundsResponse>({
queryKey: ["/api/paper/funds"], queryKey: ["/paper/funds"],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", "/api/paper/funds"); const res = await apiRequest("GET", "/paper/funds");
return res.json(); return res.json();
}, },
}); });
const positionsQuery = useQuery<PaperPositionsResponse>({ const positionsQuery = useQuery<PaperPositionsResponse>({
queryKey: ["/api/paper/positions"], queryKey: ["/paper/positions"],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", "/api/paper/positions"); const res = await apiRequest("GET", "/paper/positions");
return res.json(); return res.json();
}, },
}); });
const ordersQuery = useQuery<PaperOrdersResponse>({ const ordersQuery = useQuery<PaperOrdersResponse>({
queryKey: ["/api/paper/orders"], queryKey: ["/paper/orders"],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", "/api/paper/orders"); const res = await apiRequest("GET", "/paper/orders");
return res.json(); return res.json();
}, },
refetchInterval: 10000, refetchInterval: 10000,
@ -246,7 +246,7 @@ function PaperTradingPortfolio() {
let timer: number; let timer: number;
const poll = async () => { const poll = async () => {
try { try {
const res = await fetch("/api/paper/mtm", { const res = await fetch("/paper/mtm", {
credentials: "include", credentials: "include",
}); });
if (!res.ok) { if (!res.ok) {
@ -307,7 +307,7 @@ function PaperTradingPortfolio() {
useEffect(() => { useEffect(() => {
const fetchMarketStatus = async () => { const fetchMarketStatus = async () => {
try { try {
const res = await fetch("/api/market/status", { const res = await fetch("/market/status", {
credentials: "include", credentials: "include",
}); });
if (!res.ok) { if (!res.ok) {
@ -340,7 +340,7 @@ function PaperTradingPortfolio() {
setIsResetting(true); setIsResetting(true);
try { try {
const res = await fetch("/api/paper/reset", { method: "POST" }); const res = await fetch("/paper/reset", { method: "POST" });
if (!res.ok) { if (!res.ok) {
throw new Error("Reset failed"); throw new Error("Reset failed");
} }
@ -474,7 +474,7 @@ function PaperTradingPortfolio() {
setIsAddingCash(true); setIsAddingCash(true);
try { try {
await apiRequest("POST", "/api/paper/add-cash", { amount: addCashAmount }); await apiRequest("POST", "/paper/add-cash", { amount: addCashAmount });
setMtmCash((prev) => setMtmCash((prev) =>
typeof prev === "number" ? prev + addCashAmount : addCashAmount typeof prev === "number" ? prev + addCashAmount : addCashAmount
); );

View File

@ -32,7 +32,7 @@ export default function SupportPage() {
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [checking, setChecking] = useState(false); const [checking, setChecking] = useState(false);
const { data: sessionUser } = useQuery<SessionUser | null>({ const { data: sessionUser } = useQuery<SessionUser | null>({
queryKey: ["/api/me"], queryKey: ["/me"],
queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }), queryFn: getQueryFn<SessionUser | null>({ on401: "returnNull" }),
}); });
const sessionEmail = sessionUser?.username?.trim() || ""; const sessionEmail = sessionUser?.username?.trim() || "";
@ -52,7 +52,7 @@ export default function SupportPage() {
} }
setSubmitting(true); setSubmitting(true);
try { try {
const res = await apiRequest("POST", "/api/support/ticket", { const res = await apiRequest("POST", "/support/ticket", {
...form, ...form,
email: effectiveEmail, email: effectiveEmail,
}); });
@ -75,7 +75,7 @@ export default function SupportPage() {
} }
setChecking(true); setChecking(true);
try { try {
const res = await apiRequest("POST", `/api/support/ticket/status/${ticketId.trim()}`, { const res = await apiRequest("POST", `/support/ticket/status/${ticketId.trim()}`, {
email: effectiveEmail, email: effectiveEmail,
}); });
const data = await res.json(); const data = await res.json();

View File

@ -48,7 +48,7 @@ export default function ZerodhaCallback() {
return; return;
} }
const url = `/api/broker/zerodha/callback?request_token=${encodeURIComponent(params.requestToken)}`; const url = `/broker/zerodha/callback?request_token=${encodeURIComponent(params.requestToken)}`;
apiRequest("GET", url) apiRequest("GET", url)
.then(() => finalize("success")) .then(() => finalize("success"))
.catch((err: any) => finalize("error", err?.message || "Unable to complete the login.")); .catch((err: any) => finalize("error", err?.message || "Unable to complete the login."));

View File

@ -4,9 +4,9 @@ import type { InvariantsResponse } from "./types";
export default function AdminInvariants() { export default function AdminInvariants() {
const invariantsQuery = useQuery<InvariantsResponse>({ const invariantsQuery = useQuery<InvariantsResponse>({
queryKey: ["/api/admin/health/invariants"], queryKey: ["admin/health/invariants"],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", "/api/admin/health/invariants"); const res = await apiRequest("GET", "admin/health/invariants");
return res.json(); return res.json();
}, },
}); });

View File

@ -13,9 +13,9 @@ const StatCard = ({ label, value }: { label: string; value: number | string }) =
export default function AdminOverview() { export default function AdminOverview() {
const overviewQuery = useQuery<OverviewResponse>({ const overviewQuery = useQuery<OverviewResponse>({
queryKey: ["/api/admin/overview"], queryKey: ["admin/overview"],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", "/api/admin/overview"); const res = await apiRequest("GET", "admin/overview");
return res.json(); return res.json();
}, },
}); });

View File

@ -31,7 +31,7 @@ export default function AdminPage() {
const checkAccess = useCallback(async () => { const checkAccess = useCallback(async () => {
try { try {
const res = await fetch("/api/admin/overview", { credentials: "include" }); const res = await fetch("admin/overview", { credentials: "include" });
if (res.status === 401) { if (res.status === 401) {
setAccessState("unauthenticated"); setAccessState("unauthenticated");
return; return;
@ -63,7 +63,7 @@ export default function AdminPage() {
setLoginPending(true); setLoginPending(true);
setLoginError(null); setLoginError(null);
try { try {
await apiRequest("POST", "/api/login", { await apiRequest("POST", "login", {
email: loginForm.email.trim(), email: loginForm.email.trim(),
password: loginForm.password, password: loginForm.password,
}); });

View File

@ -5,9 +5,9 @@ import type { RunDetailResponse } from "./types";
export default function AdminRunDetail({ runId }: { runId: string }) { export default function AdminRunDetail({ runId }: { runId: string }) {
const detailQuery = useQuery<RunDetailResponse>({ const detailQuery = useQuery<RunDetailResponse>({
queryKey: ["/api/admin/runs", runId], queryKey: ["admin/runs", runId],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", `/api/admin/runs/${runId}`); const res = await apiRequest("GET", `admin/runs/${runId}`);
return res.json(); return res.json();
}, },
}); });

View File

@ -24,9 +24,9 @@ export default function AdminRuns() {
}, [page, pageSize, status, mode, userId]); }, [page, pageSize, status, mode, userId]);
const runsQuery = useQuery<RunsResponse>({ const runsQuery = useQuery<RunsResponse>({
queryKey: ["/api/admin/runs", page, status, mode, userId], queryKey: ["admin/runs", page, status, mode, userId],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", `/api/admin/runs?${queryString}`); const res = await apiRequest("GET", `admin/runs?${queryString}`);
return res.json(); return res.json();
}, },
}); });

View File

@ -26,7 +26,7 @@ type TicketsResponse = {
export default function AdminSupportTickets() { export default function AdminSupportTickets() {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { data, isLoading } = useQuery<TicketsResponse>({ const { data, isLoading } = useQuery<TicketsResponse>({
queryKey: ["/api/admin/support-tickets"], queryKey: ["admin/support-tickets"],
queryFn: getQueryFn<TicketsResponse>({ on401: "throw" }), queryFn: getQueryFn<TicketsResponse>({ on401: "throw" }),
}); });
@ -34,9 +34,9 @@ export default function AdminSupportTickets() {
const confirmed = window.confirm("Delete this ticket? This cannot be undone."); const confirmed = window.confirm("Delete this ticket? This cannot be undone.");
if (!confirmed) return; if (!confirmed) return;
try { try {
await apiRequest("DELETE", `/api/admin/support-tickets/${ticketId}`); await apiRequest("DELETE", `admin/support-tickets/${ticketId}`);
toast({ title: "Ticket deleted" }); toast({ title: "Ticket deleted" });
queryClient.invalidateQueries({ queryKey: ["/api/admin/support-tickets"] }); queryClient.invalidateQueries({ queryKey: ["admin/support-tickets"] });
} catch (err: any) { } catch (err: any) {
toast({ title: "Delete failed", description: err?.message || "Try again." }); toast({ title: "Delete failed", description: err?.message || "Try again." });
} }

View File

@ -5,9 +5,9 @@ import type { UserDetailResponse } from "./types";
export default function AdminUserDetail({ userId }: { userId: string }) { export default function AdminUserDetail({ userId }: { userId: string }) {
const detailQuery = useQuery<UserDetailResponse>({ const detailQuery = useQuery<UserDetailResponse>({
queryKey: ["/api/admin/users", userId], queryKey: ["admin/users", userId],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", `/api/admin/users/${userId}`); const res = await apiRequest("GET", `admin/users/${userId}`);
return res.json(); return res.json();
}, },
}); });

View File

@ -42,15 +42,15 @@ export default function AdminUsers() {
}, [page, pageSize, query]); }, [page, pageSize, query]);
const usersQuery = useQuery<UsersResponse>({ const usersQuery = useQuery<UsersResponse>({
queryKey: ["/api/admin/users", page, query], queryKey: ["admin/users", page, query],
queryFn: async () => { queryFn: async () => {
const res = await apiRequest("GET", `/api/admin/users?${queryString}`); const res = await apiRequest("GET", `admin/users?${queryString}`);
return res.json(); return res.json();
}, },
}); });
const meQuery = useQuery<{ id: string; username: string; role?: string } | null>({ const meQuery = useQuery<{ id: string; username: string; role?: string } | null>({
queryKey: ["/api/me"], queryKey: ["me"],
queryFn: getQueryFn({ on401: "returnNull" }), queryFn: getQueryFn({ on401: "returnNull" }),
}); });
@ -66,13 +66,13 @@ export default function AdminUsers() {
try { try {
const res = await apiRequest( const res = await apiRequest(
"DELETE", "DELETE",
`/api/admin/users/${deleteTarget.user_id}?hard=true`, `admin/users/${deleteTarget.user_id}?hard=true`,
); );
(await res.json()) as DeleteUserResponse; (await res.json()) as DeleteUserResponse;
setDeleteTarget(null); setDeleteTarget(null);
setConfirmChecked(false); setConfirmChecked(false);
await queryClient.invalidateQueries({ queryKey: ["/api/admin/users"] }); await queryClient.invalidateQueries({ queryKey: ["admin/users"] });
await queryClient.invalidateQueries({ queryKey: ["/api/admin/overview"] }); await queryClient.invalidateQueries({ queryKey: ["admin/overview"] });
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : "Delete failed"; const message = err instanceof Error ? err.message : "Delete failed";
setDeleteError(message); setDeleteError(message);
@ -90,13 +90,13 @@ export default function AdminUsers() {
try { try {
const res = await apiRequest( const res = await apiRequest(
"POST", "POST",
`/api/admin/users/${resetTarget.user_id}/hard-reset`, `admin/users/${resetTarget.user_id}/hard-reset`,
); );
(await res.json()) as HardResetResponse; (await res.json()) as HardResetResponse;
setResetTarget(null); setResetTarget(null);
setResetChecked(false); setResetChecked(false);
await queryClient.invalidateQueries({ queryKey: ["/api/admin/users"] }); await queryClient.invalidateQueries({ queryKey: ["admin/users"] });
await queryClient.invalidateQueries({ queryKey: ["/api/admin/overview"] }); await queryClient.invalidateQueries({ queryKey: ["admin/overview"] });
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : "Reset failed"; const message = err instanceof Error ? err.message : "Reset failed";
setResetError(message); setResetError(message);
@ -180,8 +180,8 @@ export default function AdminUsers() {
isSelf={user.user_id === currentUserId} isSelf={user.user_id === currentUserId}
isSuperAdmin={isSuperAdmin} isSuperAdmin={isSuperAdmin}
onUpdated={() => { onUpdated={() => {
queryClient.invalidateQueries({ queryKey: ["/api/admin/users"] }); queryClient.invalidateQueries({ queryKey: ["admin/users"] });
queryClient.invalidateQueries({ queryKey: ["/api/admin/overview"] }); queryClient.invalidateQueries({ queryKey: ["admin/overview"] });
}} }}
/> />
<Button <Button

View File

@ -58,7 +58,7 @@ export default function RoleActions({ target, isSelf, isSuperAdmin, onUpdated }:
try { try {
await apiRequest( await apiRequest(
"POST", "POST",
`/api/admin/users/${target.user_id}/${actionEndpoint[action]}`, `admin/users/${target.user_id}/${actionEndpoint[action]}`,
); );
onUpdated(); onUpdated();
setAction(null); setAction(null);