From 98894617e2464c56bc0921b94f131e8093ba7415 Mon Sep 17 00:00:00 2001 From: Thigazhezhilan J Date: Sat, 2 May 2026 12:47:29 +0530 Subject: [PATCH] Add Auto-Login setup card to portfolio page - New AutoLoginSetup component: form to save Zerodha credentials and TOTP secret, shows last refresh time, error state, manual refresh and remove buttons - New autoLogin.ts API client for all auto-login endpoints - AutoLoginSetup card rendered in PortfolioSection when broker is connected Co-Authored-By: Claude Sonnet 4.6 --- src/api/autoLogin.ts | 33 +++ src/components/AutoLoginSetup.tsx | 227 ++++++++++++++++++++ src/components/landing/PortfolioSection.tsx | 10 + 3 files changed, 270 insertions(+) create mode 100644 src/api/autoLogin.ts create mode 100644 src/components/AutoLoginSetup.tsx diff --git a/src/api/autoLogin.ts b/src/api/autoLogin.ts new file mode 100644 index 00000000..220b0004 --- /dev/null +++ b/src/api/autoLogin.ts @@ -0,0 +1,33 @@ +import { apiRequest } from "@/lib/queryClient"; + +export type AutoLoginStatus = { + configured: boolean; + last_refreshed_at: string | null; + last_error: string | null; +}; + +export type AutoLoginSetupRequest = { + zerodha_login_id: string; + password: string; + totp_secret: string; +}; + +export async function getAutoLoginStatus(): Promise { + const res = await apiRequest("GET", "/auto-login/status"); + return res.json(); +} + +export async function setupAutoLogin(data: AutoLoginSetupRequest): Promise<{ configured: boolean; message: string }> { + const res = await apiRequest("POST", "/auto-login/setup", data); + return res.json(); +} + +export async function removeAutoLogin(): Promise<{ configured: boolean; message: string }> { + const res = await apiRequest("DELETE", "/auto-login/setup"); + return res.json(); +} + +export async function triggerAutoLogin(): Promise<{ success: boolean; message: string }> { + const res = await apiRequest("POST", "/auto-login/trigger"); + return res.json(); +} diff --git a/src/components/AutoLoginSetup.tsx b/src/components/AutoLoginSetup.tsx new file mode 100644 index 00000000..2af780fd --- /dev/null +++ b/src/components/AutoLoginSetup.tsx @@ -0,0 +1,227 @@ +import { useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { ShieldCheck, ShieldOff, RefreshCw, Eye, EyeOff, Trash2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; +import { toast } from "@/hooks/use-toast"; +import { + getAutoLoginStatus, + setupAutoLogin, + removeAutoLogin, + triggerAutoLogin, +} from "@/api/autoLogin"; + +export default function AutoLoginSetup() { + const queryClient = useQueryClient(); + const [showForm, setShowForm] = useState(false); + const [showPassword, setShowPassword] = useState(false); + const [showTotp, setShowTotp] = useState(false); + const [form, setForm] = useState({ zerodha_login_id: "", password: "", totp_secret: "" }); + + const { data: status, isLoading } = useQuery({ + queryKey: ["/auto-login/status"], + queryFn: getAutoLoginStatus, + refetchInterval: 60_000, + }); + + const setupMutation = useMutation({ + mutationFn: setupAutoLogin, + onSuccess: () => { + toast({ title: "Auto-login enabled", description: "Your Zerodha session will refresh automatically every morning." }); + setShowForm(false); + setForm({ zerodha_login_id: "", password: "", totp_secret: "" }); + queryClient.invalidateQueries({ queryKey: ["/auto-login/status"] }); + }, + onError: (err: any) => { + toast({ title: "Setup failed", description: err?.message ?? "Could not verify credentials. Check and try again.", variant: "destructive" }); + }, + }); + + const removeMutation = useMutation({ + mutationFn: removeAutoLogin, + onSuccess: () => { + toast({ title: "Auto-login removed", description: "You will need to reconnect Zerodha manually each day." }); + queryClient.invalidateQueries({ queryKey: ["/auto-login/status"] }); + }, + }); + + const triggerMutation = useMutation({ + mutationFn: triggerAutoLogin, + onSuccess: () => { + toast({ title: "Session refreshed", description: "Zerodha token has been refreshed successfully." }); + queryClient.invalidateQueries({ queryKey: ["/auto-login/status"] }); + }, + onError: (err: any) => { + toast({ title: "Refresh failed", description: err?.message ?? "Could not refresh session.", variant: "destructive" }); + }, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!form.zerodha_login_id || !form.password || !form.totp_secret) { + toast({ title: "All fields are required", variant: "destructive" }); + return; + } + setupMutation.mutate(form); + }; + + if (isLoading) return null; + + const configured = status?.configured ?? false; + const lastRefreshed = status?.last_refreshed_at + ? new Date(status.last_refreshed_at).toLocaleString("en-IN", { timeZone: "Asia/Kolkata", dateStyle: "medium", timeStyle: "short" }) + : null; + const hasError = !!status?.last_error; + + return ( +
+ {/* Header */} +
+
+ + Auto-Login +
+ {configured ? ( + + Active + + ) : ( + + Not configured + + )} +
+ + {/* Status info */} + {configured && ( +
+ {lastRefreshed &&

Last refreshed: {lastRefreshed} IST

} + {hasError && ( +

Last error: {status!.last_error}

+ )} +

Token refreshes automatically at 6:05 AM IST daily.

+
+ )} + + {!configured && !showForm && ( +

+ Enable auto-login so QuantFortune can refresh your Zerodha session every morning — your strategy runs uninterrupted. +

+ )} + + {/* Action buttons when configured */} + {configured && ( +
+ + +
+ )} + + {/* Setup button */} + {!configured && !showForm && ( + + )} + + {/* Setup form */} + {!configured && showForm && ( +
+
+ + setForm((f) => ({ ...f, zerodha_login_id: e.target.value }))} + /> +
+ +
+ +
+ setForm((f) => ({ ...f, password: e.target.value }))} + /> + +
+
+ +
+ +
+ setForm((f) => ({ ...f, totp_secret: e.target.value }))} + /> + +
+

+ Find this in Zerodha → My Profile → Account Security → 2FA → View TOTP secret key. +

+
+ +
+ + +
+
+ )} +
+ ); +} diff --git a/src/components/landing/PortfolioSection.tsx b/src/components/landing/PortfolioSection.tsx index e5acf193..d41565a9 100644 --- a/src/components/landing/PortfolioSection.tsx +++ b/src/components/landing/PortfolioSection.tsx @@ -19,6 +19,7 @@ import { SelectValue, } from "@/components/ui/select"; import StrategyTimeline from "@/components/StrategyTimeline"; +import AutoLoginSetup from "@/components/AutoLoginSetup"; import { toast } from "@/hooks/use-toast"; import { ChartContainer, @@ -1667,6 +1668,15 @@ export default function PortfolioSection() { + {isConnected && ( +
+ +
+ )} +