fix: restore toast auto-close and implement authentication flow

main
mula.liu 2025-12-02 11:21:51 +08:00
parent e2a9052e57
commit d567377835
3 changed files with 44 additions and 87 deletions

View File

@ -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}
/>

View File

@ -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)
<div className="flex flex-col items-center -translate-y-24 pointer-events-none">
<style>{terminalStyles}</style>
<style>{styles}</style>
{/* Main Info Card */}
<div className="bg-black/80 backdrop-blur-xl border border-white/10 rounded-2xl p-5 min-w-[340px] max-w-md shadow-2xl pointer-events-auto relative group mb-2">
@ -165,54 +137,22 @@ export function FocusInfo({ body, onClose }: FocusInfoProps) {
{/* Connecting Line/Triangle pointing down to the body */}
<div className="w-0 h-0 border-l-[8px] border-l-transparent border-r-[8px] border-r-transparent border-t-[8px] border-t-black/80 backdrop-blur-xl mt-[-1px]"></div>
{/* Terminal Modal */}
<Modal
<TerminalModal
open={showTerminal}
onCancel={() => 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={
<div className="flex items-center gap-2 text-[#2ea043] font-mono tracking-wider text-xs">
<div className="w-2 h-2 rounded-full bg-[#2ea043] animate-pulse"></div>
JPL/HORIZONS SYSTEM INTERFACE // {body.name.toUpperCase()}
</div>
}
closeIcon={<X size={18} style={{ color: '#2ea043' }} />}
loading={loading}
loadingText="ESTABLISHING SECURE UPLINK..."
>
<div className="h-[60vh] overflow-auto font-mono text-xs whitespace-pre-wrap scrollbar-none">
{loading ? (
<div className="flex items-center justify-center h-full flex-col gap-4 text-[#2ea043]">
<Spin indicator={<Radar className="animate-spin text-[#2ea043]" size={48} />} />
<div className="animate-pulse tracking-widest">ESTABLISHING SECURE UPLINK...</div>
<div className="text-[10px] opacity-50">Connecting to ssd.jpl.nasa.gov...</div>
</div>
) : (
<div className="animate-in fade-in duration-500">
{terminalData}
</div>
)}
<div className="whitespace-pre-wrap">
{terminalData}
</div>
</Modal>
</TerminalModal>
</div>
);
}

View File

@ -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<Map<string, number>>(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 (
<ToastContext.Provider value={{ showToast, success, error, warning, info, removeToast }}>
@ -101,7 +109,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
`}
>
<div className="mt-0.5 shrink-0">{icons[toast.type]}</div>
<p className="flex-1 text-sm font-medium leading-tight pt-0.5">{toast.message}</p>
<div className="flex-1 text-sm font-medium leading-tight pt-0.5">{toast.message}</div>
<button
onClick={() => removeToast(toast.id)}
className="text-white/40 hover:text-white transition-colors shrink-0"