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>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<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 property="og:title" content="QuantFortune | Long-Term Investment Strategies" />
<meta property="og:description" content="Simple, disciplined investment strategies for salaried investors." />
<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<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">
<script type="module" crossorigin src="/assets/index-z66hXP0q.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-ButmEv-s.css">
</head>
<body>
<div id="root"></div>
</body>
<!DOCTYPE html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<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 property="og:title" content="QuantFortune | Long-Term Investment Strategies" />
<meta property="og:description" content="Simple, disciplined investment strategies for salaried investors." />
<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<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">
<script type="module" crossorigin src="/assets/index-Dko5e3BG.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Dq1pAiY6.css">
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -48,7 +48,7 @@ export default function ZerodhaCallback() {
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)
.then(() => finalize("success"))
.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() {
const invariantsQuery = useQuery<InvariantsResponse>({
queryKey: ["/api/admin/health/invariants"],
queryKey: ["admin/health/invariants"],
queryFn: async () => {
const res = await apiRequest("GET", "/api/admin/health/invariants");
const res = await apiRequest("GET", "admin/health/invariants");
return res.json();
},
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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