import { useState, useEffect, useCallback, useRef } from 'react'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; import { Spinner } from '@/components/ui/Spinner'; import { useLoginStatus } from '@/hooks/useLoginStatus'; import { useToast } from '@/context/ToastContext'; import { getLoginQRCode, deleteCookies, getLoginStatus } from '@/api/endpoints'; export function LoginPage() { const { status, loading: statusLoading, refresh: refreshStatus } = useLoginStatus(); const { toast } = useToast(); const [qrData, setQrData] = useState(null); const [qrLoading, setQrLoading] = useState(false); const [polling, setPolling] = useState(false); const [countdown, setCountdown] = useState(0); const [logoutLoading, setLogoutLoading] = useState(false); const pollRef = useRef | undefined>(undefined); const countdownRef = useRef | undefined>(undefined); const stopPolling = useCallback(() => { setPolling(false); if (pollRef.current) clearInterval(pollRef.current); if (countdownRef.current) clearInterval(countdownRef.current); }, []); const handleGetQR = useCallback(async () => { stopPolling(); setQrLoading(true); setQrData(null); try { const res = await getLoginQRCode(); if (res.success && res.data) { if (res.data.alreadyLoggedIn) { toast('info', 'Already logged in!'); void refreshStatus(); return; } setQrData(res.data.qrcodeData); // Start polling setPolling(true); setCountdown(240); // 4 min pollRef.current = setInterval(async () => { try { const statusRes = await getLoginStatus(); if (statusRes.success && statusRes.data?.loggedIn) { stopPolling(); setQrData(null); toast('success', `Logged in as ${statusRes.data.username || 'user'}`); void refreshStatus(); } } catch { // ignore poll errors } }, 3000); countdownRef.current = setInterval(() => { setCountdown((prev) => { if (prev <= 1) { stopPolling(); setQrData(null); toast('warning', 'QR code expired'); return 0; } return prev - 1; }); }, 1000); } else { toast('error', res.error?.message || 'Failed to get QR code'); } } catch (err) { toast('error', err instanceof Error ? err.message : 'Failed to get QR code'); } finally { setQrLoading(false); } }, [stopPolling, toast, refreshStatus]); const handleLogout = useCallback(async () => { setLogoutLoading(true); try { const res = await deleteCookies(); if (res.success) { toast('success', 'Logged out successfully'); void refreshStatus(); } else { toast('error', res.error?.message || 'Failed to logout'); } } catch (err) { toast('error', err instanceof Error ? err.message : 'Failed to logout'); } finally { setLogoutLoading(false); } }, [toast, refreshStatus]); useEffect(() => { return () => stopPolling(); }, [stopPolling]); const formatCountdown = (secs: number) => { const m = Math.floor(secs / 60); const s = secs % 60; return `${m}:${s.toString().padStart(2, '0')}`; }; return (

Xiaohongshu Login

{/* Current Status */}

Current Status

{statusLoading ? ( ) : status ? (
{status.loggedIn ? 'Logged In' : 'Not Logged In'} {status.username && {status.username}}
) : ( Unable to check )}
{status?.loggedIn && ( )}
{/* QR Code Login */}

QR Code Login

{!qrData && !qrLoading && (

Click the button to generate a QR code for login

)} {qrLoading && (

Generating QR code...

)} {qrData && (
Login QR Code

Scan with Xiaohongshu app to login

{polling && (
Waiting for scan... {formatCountdown(countdown)}
)}
)}
); }