From d567377835fc668515131844bd2c1d5a62c072c1 Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Tue, 2 Dec 2025 11:21:51 +0800 Subject: [PATCH] fix: restore toast auto-close and implement authentication flow --- frontend/src/App.tsx | 17 ++++-- frontend/src/components/FocusInfo.tsx | 78 +++----------------------- frontend/src/contexts/ToastContext.tsx | 36 +++++++----- 3 files changed, 44 insertions(+), 87 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a021043..e9f21c4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -103,14 +103,15 @@ function App() { // Screenshot handler with auth check const handleScreenshot = useCallback(() => { if (!user) { - toast.warning('请先登录以拍摄宇宙快照'); - setShowAuthModal(true); + toast.warning('请先登录以拍摄宇宙快照', 3000, () => { + setShowAuthModal(true); + }); return; } // Use username or full_name or fallback const nickname = user.full_name || user.username || 'Explorer'; takeScreenshot(nickname); - }, [user, takeScreenshot]); + }, [user, takeScreenshot, toast]); // Auth handlers const handleLoginSuccess = (userData: any) => { @@ -164,7 +165,15 @@ function App() { isSoundOn={isSoundOn} onToggleSound={() => setIsSoundOn(!isSoundOn)} showMessageBoard={showMessageBoard} - onToggleMessageBoard={() => setShowMessageBoard(!showMessageBoard)} + onToggleMessageBoard={() => { + if (!user) { + toast.warning('请先登录以访问留言板', 3000, () => { + setShowAuthModal(true); + }); + } else { + setShowMessageBoard(!showMessageBoard); + } + }} onScreenshot={handleScreenshot} /> diff --git a/frontend/src/components/FocusInfo.tsx b/frontend/src/components/FocusInfo.tsx index 91947fb..2dbff66 100644 --- a/frontend/src/components/FocusInfo.tsx +++ b/frontend/src/components/FocusInfo.tsx @@ -41,35 +41,7 @@ export function FocusInfo({ body, onClose }: FocusInfoProps) { } }; - const terminalStyles = ` - .terminal-modal .ant-modal-content { - background-color: #0d1117 !important; - border: 1px solid #238636 !important; - box-shadow: 0 0 30px rgba(35, 134, 54, 0.15) !important; - color: #2ea043 !important; - padding: 0 !important; - overflow: hidden !important; - } - .terminal-modal .ant-modal-body { - background-color: #0d1117 !important; - } - .terminal-modal .ant-modal-header { - background-color: #161b22 !important; - border-bottom: 1px solid #238636 !important; - margin-bottom: 0 !important; - } - .terminal-modal .ant-modal-title { - color: #2ea043 !important; - } - .terminal-modal .ant-modal-close { - color: #2ea043 !important; - } - .terminal-modal .ant-modal-close:hover { - background-color: rgba(35, 134, 54, 0.2) !important; - } - .terminal-modal .ant-modal-body .animate-in { - color: #2ea043 !important; /* Ensure content text is green */ - } + const styles = ` @keyframes spin-slow { from { transform: rotate(0deg); } to { transform: rotate(360deg); } @@ -82,7 +54,7 @@ export function FocusInfo({ body, onClose }: FocusInfoProps) { return ( // Remove fixed positioning, now handled by parent container (Html component in 3D)
- + {/* Main Info Card */}
@@ -165,54 +137,22 @@ export function FocusInfo({ body, onClose }: FocusInfoProps) { {/* Connecting Line/Triangle pointing down to the body */}
- {/* Terminal Modal */} - setShowTerminal(false)} - footer={null} - width={800} - centered - className="terminal-modal" - styles={{ - header: { - backgroundColor: '#161b22', - borderBottom: '1px solid #238636', - padding: '12px 20px', - marginBottom: 0, - display: 'flex', - alignItems: 'center' - }, - mask: { - backgroundColor: 'rgba(0, 0, 0, 0.85)', - backdropFilter: 'blur(4px)' - }, - body: { - padding: '20px', - backgroundColor: '#0d1117' - } - }} + onClose={() => setShowTerminal(false)} title={
JPL/HORIZONS SYSTEM INTERFACE // {body.name.toUpperCase()}
} - closeIcon={} + loading={loading} + loadingText="ESTABLISHING SECURE UPLINK..." > -
- {loading ? ( -
- } /> -
ESTABLISHING SECURE UPLINK...
-
Connecting to ssd.jpl.nasa.gov...
-
- ) : ( -
- {terminalData} -
- )} +
+ {terminalData}
- +
); } diff --git a/frontend/src/contexts/ToastContext.tsx b/frontend/src/contexts/ToastContext.tsx index d1d9bcc..bd49420 100644 --- a/frontend/src/contexts/ToastContext.tsx +++ b/frontend/src/contexts/ToastContext.tsx @@ -1,4 +1,5 @@ import { createContext, useContext, useState, useCallback, useRef } from 'react'; +import type { ReactNode } from 'react'; import { X, CheckCircle, AlertCircle, AlertTriangle, Info } from 'lucide-react'; // Types @@ -7,16 +8,17 @@ type ToastType = 'success' | 'error' | 'warning' | 'info'; interface Toast { id: string; type: ToastType; - message: string; + message: ReactNode; duration?: number; + onClose?: () => void; } interface ToastContextValue { - showToast: (message: string, type?: ToastType, duration?: number) => string; - success: (message: string, duration?: number) => string; - error: (message: string, duration?: number) => string; - warning: (message: string, duration?: number) => string; - info: (message: string, duration?: number) => string; + showToast: (message: ReactNode, type?: ToastType, duration?: number, onClose?: () => void) => string; + success: (message: ReactNode, duration?: number, onClose?: () => void) => string; + error: (message: ReactNode, duration?: number, onClose?: () => void) => string; + warning: (message: ReactNode, duration?: number, onClose?: () => void) => string; + info: (message: ReactNode, duration?: number, onClose?: () => void) => string; removeToast: (id: string) => void; } @@ -54,16 +56,22 @@ export function ToastProvider({ children }: { children: React.ReactNode }) { const timersRef = useRef>(new Map()); const removeToast = useCallback((id: string) => { - setToasts((prev) => prev.filter((t) => t.id !== id)); + setToasts((prev) => { + const toast = prev.find(t => t.id === id); + if (toast && toast.onClose) { + toast.onClose(); + } + return prev.filter((t) => t.id !== id); + }); if (timersRef.current.has(id)) { clearTimeout(timersRef.current.get(id)); timersRef.current.delete(id); } }, []); - const showToast = useCallback((message: string, type: ToastType = 'info', duration = 3000) => { + const showToast = useCallback((message: ReactNode, type: ToastType = 'info', duration = 3000, onClose?: () => void) => { const id = Math.random().toString(36).substring(2, 9); - const newToast: Toast = { id, type, message, duration }; + const newToast: Toast = { id, type, message, duration, onClose }; setToasts((prev) => [...prev, newToast]); @@ -78,10 +86,10 @@ export function ToastProvider({ children }: { children: React.ReactNode }) { }, [removeToast]); // Convenience methods - const success = useCallback((msg: string, d?: number) => showToast(msg, 'success', d), [showToast]); - const error = useCallback((msg: string, d?: number) => showToast(msg, 'error', d), [showToast]); - const warning = useCallback((msg: string, d?: number) => showToast(msg, 'warning', d), [showToast]); - const info = useCallback((msg: string, d?: number) => showToast(msg, 'info', d), [showToast]); + const success = useCallback((msg: ReactNode, d?: number, onClose?: () => void) => showToast(msg, 'success', d, onClose), [showToast]); + const error = useCallback((msg: ReactNode, d = 3000, onClose?: () => void) => showToast(msg, 'error', d, onClose), [showToast]); + const warning = useCallback((msg: ReactNode, d = 3000, onClose?: () => void) => showToast(msg, 'warning', d, onClose), [showToast]); + const info = useCallback((msg: ReactNode, d?: number, onClose?: () => void) => showToast(msg, 'info', d, onClose), [showToast]); return ( @@ -101,7 +109,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) { `} >
{icons[toast.type]}
-

{toast.message}

+
{toast.message}