import type { TranslationContext } from './TranslationEngine'; import { TemplateEngineError } from './TemplateEngineError'; import { createLogger } from '../utils/Logger'; const logger = createLogger('ErrorDisplay'); /** * 에러 표시 옵션 */ export interface ErrorDisplayOptions { /** 에러 제목 */ title: string; /** 사용자 메시지 */ message: string; /** 상세 메시지 (API 응답 메시지 등) */ detailMessage?: string; /** Font Awesome 아이콘 클래스 */ icon: string; /** Stack Trace 표시 여부 */ showStack: boolean; /** Stack Trace 문자열 */ stackTrace?: string; /** 새로고침 버튼 표시 여부 */ showReloadButton: boolean; /** 디버그 모드 여부 */ debug: boolean; } /** * 에러 화면 렌더링 컴포넌트 * * 주의: 이 컴포넌트는 템플릿 엔진 초기화 실패 시에도 사용되므로 * Tailwind CSS 클래스 대신 인라인 스타일을 사용합니다. * 초기화 실패 시점에는 CSS가 아직 로드되지 않았을 수 있기 때문입니다. */ export class ErrorDisplay { /** * 에러 화면을 렌더링합니다. * * @param containerId 컨테이너 요소 ID * @param options 에러 표시 옵션 */ static render(containerId: string, options: ErrorDisplayOptions): void { const container = document.getElementById(containerId); if (!container) { logger.error(`Container #${containerId} not found`); return; } // 스택 트레이스 표시 여부 결정 const shouldShowStack = options.debug && options.showStack && options.stackTrace; // 다크 모드 감지 const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; // 인라인 스타일 정의 (CSS 의존성 없이 동작) const styles = { container: ` display: flex; align-items: center; justify-content: center; min-height: 100vh; background-color: ${isDarkMode ? '#111827' : '#f9fafb'}; padding: 1rem; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; `, card: ` max-width: 28rem; width: 100%; background-color: ${isDarkMode ? '#1f2937' : '#ffffff'}; border-radius: 0.5rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); padding: 2rem; text-align: center; `, iconContainer: ` margin-bottom: 1.5rem; `, icon: ` font-size: 4rem; color: #ef4444; `, title: ` font-size: 1.5rem; font-weight: 700; color: ${isDarkMode ? '#ffffff' : '#111827'}; margin-bottom: 1rem; `, message: ` color: ${isDarkMode ? '#d1d5db' : '#4b5563'}; margin-bottom: 0.5rem; line-height: 1.625; `, detailMessage: ` font-size: 0.875rem; color: ${isDarkMode ? '#9ca3af' : '#6b7280'}; margin-bottom: 1.5rem; background-color: ${isDarkMode ? '#374151' : '#f3f4f6'}; padding: 0.5rem 0.75rem; border-radius: 0.25rem; `, spacer: ` margin-bottom: 1.5rem; `, stackContainer: ` margin-top: 1.5rem; padding: 1rem; background-color: ${isDarkMode ? '#374151' : '#f3f4f6'}; border-radius: 0.375rem; text-align: left; `, stackTitle: ` font-size: 0.875rem; font-weight: 600; color: ${isDarkMode ? '#e5e7eb' : '#374151'}; margin-bottom: 0.5rem; `, stackTrace: ` font-size: 0.75rem; color: ${isDarkMode ? '#d1d5db' : '#4b5563'}; overflow-x: auto; white-space: pre-wrap; word-break: break-word; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; `, buttonContainer: ` margin-top: 1.5rem; `, button: ` display: inline-flex; align-items: center; padding: 0.75rem 1.5rem; background-color: #2563eb; color: #ffffff; font-weight: 500; border-radius: 0.375rem; border: none; cursor: pointer; transition: background-color 0.2s; `, }; container.innerHTML = `
${this.escapeHtml(options.message)}
${ options.detailMessage ? `${this.escapeHtml(options.detailMessage)}
` : `` } ${ shouldShowStack ? `Stack Trace:
${this.escapeHtml(
options.stackTrace
)}