- Database schema with 31 tables for 4-card system - LLM API integration (Gemini, DeepSeek, OpenAI) - Error handling system with modal component - Connection test UI for LLM services - Environment configuration files - Complete database documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
128 lines
3.4 KiB
JavaScript
128 lines
3.4 KiB
JavaScript
/**
|
||
* 錯誤彈窗元件
|
||
* 統一顯示應用程式中的錯誤訊息
|
||
*/
|
||
|
||
import React, { useEffect, useState } from 'react';
|
||
import './ErrorModal.css';
|
||
|
||
const ErrorModal = ({ error, onClose, autoClose = true, duration = 5000 }) => {
|
||
const [isVisible, setIsVisible] = useState(false);
|
||
const [countdown, setCountdown] = useState(duration / 1000);
|
||
|
||
useEffect(() => {
|
||
if (error) {
|
||
setIsVisible(true);
|
||
setCountdown(duration / 1000);
|
||
|
||
if (autoClose) {
|
||
const timer = setTimeout(() => {
|
||
handleClose();
|
||
}, duration);
|
||
|
||
const countdownInterval = setInterval(() => {
|
||
setCountdown((prev) => Math.max(0, prev - 1));
|
||
}, 1000);
|
||
|
||
return () => {
|
||
clearTimeout(timer);
|
||
clearInterval(countdownInterval);
|
||
};
|
||
}
|
||
}
|
||
}, [error, autoClose, duration]);
|
||
|
||
const handleClose = () => {
|
||
setIsVisible(false);
|
||
setTimeout(() => {
|
||
if (onClose) onClose();
|
||
}, 300);
|
||
};
|
||
|
||
if (!error) return null;
|
||
|
||
const getSeverityClass = () => {
|
||
if (!error.statusCode) return 'error';
|
||
if (error.statusCode >= 500) return 'error';
|
||
if (error.statusCode >= 400) return 'warning';
|
||
return 'info';
|
||
};
|
||
|
||
const getSeverityIcon = () => {
|
||
const severity = getSeverityClass();
|
||
switch (severity) {
|
||
case 'error':
|
||
return '❌';
|
||
case 'warning':
|
||
return '⚠️';
|
||
case 'info':
|
||
return 'ℹ️';
|
||
default:
|
||
return '❌';
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className={`error-modal-overlay ${isVisible ? 'visible' : ''}`} onClick={handleClose}>
|
||
<div
|
||
className={`error-modal ${getSeverityClass()} ${isVisible ? 'visible' : ''}`}
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
{/* Header */}
|
||
<div className="error-modal-header">
|
||
<div className="error-modal-title">
|
||
<span className="error-icon">{getSeverityIcon()}</span>
|
||
<h3>{error.title || '錯誤'}</h3>
|
||
</div>
|
||
<button className="error-modal-close" onClick={handleClose} aria-label="關閉">
|
||
✕
|
||
</button>
|
||
</div>
|
||
|
||
{/* Body */}
|
||
<div className="error-modal-body">
|
||
<p className="error-message">{error.message}</p>
|
||
|
||
{error.statusCode && (
|
||
<div className="error-metadata">
|
||
<span className="error-code">錯誤代碼: {error.statusCode}</span>
|
||
{error.timestamp && (
|
||
<span className="error-time">
|
||
時間: {new Date(error.timestamp).toLocaleString('zh-TW')}
|
||
</span>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{error.details && (
|
||
<details className="error-details">
|
||
<summary>詳細資訊</summary>
|
||
<pre>{JSON.stringify(error.details, null, 2)}</pre>
|
||
</details>
|
||
)}
|
||
|
||
{error.path && (
|
||
<div className="error-path">
|
||
<small>路徑: {error.path}</small>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Footer */}
|
||
<div className="error-modal-footer">
|
||
{autoClose && (
|
||
<span className="error-countdown">
|
||
{countdown > 0 ? `${countdown} 秒後自動關閉` : '關閉中...'}
|
||
</span>
|
||
)}
|
||
<button className="error-modal-button" onClick={handleClose}>
|
||
確定
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ErrorModal;
|