✨ 주요 개선사항: - any 타입 83개에서 62개로 21개 수정 (25% 감소) - 모든 ESLint 에러 11개 → 0개 완전 해결 - 타입 안전성 대폭 향상으로 런타임 오류 가능성 감소 🔧 수정된 파일들: • PWADebug.tsx - 사용하지 않는 import들에 _ prefix 추가 • categoryUtils.ts - 불필요한 any 캐스트 제거 • TransactionsHeader.tsx - BudgetData 인터페이스 정의 • storageUtils.ts - generic 타입과 unknown 타입 적용 • 각종 error handler들 - Error | {message?: string} 타입 적용 • test 파일들 - 적절한 mock 인터페이스 정의 • 유틸리티 파일들 - any → unknown 또는 적절한 타입으로 교체 🏆 성과: - 코드 품질 크게 향상 (280 → 80 문제로 71% 감소) - TypeScript 컴파일러의 타입 체크 효과성 증대 - 개발자 경험 개선 (IDE 자동완성, 타입 추론 등) 🧹 추가 정리: - ESLint no-console/no-alert 경고 해결 - Prettier 포맷팅 적용으로 코드 스타일 통일 🎯 다음 단계: 남은 62개 any 타입 계속 개선 예정 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
263 lines
8.5 KiB
HTML
263 lines
8.5 KiB
HTML
<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>긴급 복구 - Zellyy Finance</title>
|
|
<style>
|
|
body {
|
|
font-family:
|
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
max-width: 600px;
|
|
margin: 50px auto;
|
|
padding: 20px;
|
|
background: #f5f5f5;
|
|
}
|
|
.container {
|
|
background: white;
|
|
padding: 30px;
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
button {
|
|
background: #007bff;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
margin: 5px;
|
|
font-size: 14px;
|
|
}
|
|
button:hover {
|
|
background: #0056b3;
|
|
}
|
|
.danger {
|
|
background: #dc3545;
|
|
}
|
|
.danger:hover {
|
|
background: #c82333;
|
|
}
|
|
.success {
|
|
background: #28a745;
|
|
}
|
|
.success:hover {
|
|
background: #218838;
|
|
}
|
|
.status {
|
|
margin: 10px 0;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
font-family: monospace;
|
|
}
|
|
.error {
|
|
background: #f8d7da;
|
|
color: #721c24;
|
|
}
|
|
.info {
|
|
background: #d1ecf1;
|
|
color: #0c5460;
|
|
}
|
|
.success-msg {
|
|
background: #d4edda;
|
|
color: #155724;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🚨 Zellyy Finance 긴급 복구</h1>
|
|
<p>
|
|
무한 새로고침이나 청크 로딩 오류가 발생했을 때 사용하는 복구 도구입니다.
|
|
</p>
|
|
|
|
<div id="status"></div>
|
|
|
|
<h3>1단계: 저장소 초기화</h3>
|
|
<button onclick="clearStorage()">모든 저장소 초기화</button>
|
|
<button onclick="clearChunkErrors()">청크 오류 플래그만 삭제</button>
|
|
|
|
<h3>2단계: 캐시 초기화</h3>
|
|
<button onclick="clearCaches()">브라우저 캐시 삭제</button>
|
|
<button onclick="clearServiceWorkers()">서비스 워커 삭제</button>
|
|
|
|
<h3>3단계: 앱 접속</h3>
|
|
<button class="success" onclick="goToApp()">앱으로 이동</button>
|
|
<button class="success" onclick="goToAppNoChunkHandler()">
|
|
청크 핸들러 비활성화로 이동
|
|
</button>
|
|
|
|
<h3>4단계: 긴급 상황</h3>
|
|
<button class="danger" onclick="emergencyReset()">
|
|
완전 초기화 후 앱 이동
|
|
</button>
|
|
|
|
<hr />
|
|
|
|
<h3>📊 현재 상태</h3>
|
|
<button onclick="checkStatus()">상태 확인</button>
|
|
<div id="statusInfo"></div>
|
|
</div>
|
|
|
|
<script>
|
|
function showStatus(message, type = "info") {
|
|
const status = document.getElementById("status");
|
|
status.innerHTML = `<div class="${type}">${message}</div>`;
|
|
}
|
|
|
|
async function clearStorage() {
|
|
try {
|
|
sessionStorage.clear();
|
|
localStorage.clear();
|
|
showStatus("✅ 모든 저장소가 초기화되었습니다.", "success-msg");
|
|
} catch (e) {
|
|
showStatus("❌ 저장소 초기화 실패: " + e.message, "error");
|
|
}
|
|
}
|
|
|
|
function clearChunkErrors() {
|
|
try {
|
|
sessionStorage.removeItem("lastChunkErrorTime");
|
|
sessionStorage.removeItem("chunkRefreshCount");
|
|
sessionStorage.removeItem("chunkLoadErrorMaxRetries");
|
|
sessionStorage.removeItem("skipClerk");
|
|
sessionStorage.removeItem("disableClerk");
|
|
sessionStorage.removeItem("chunk-retry-refresh");
|
|
sessionStorage.removeItem("chunk-retry-refresh-time");
|
|
showStatus(
|
|
"✅ 청크 오류 관련 플래그가 삭제되었습니다.",
|
|
"success-msg"
|
|
);
|
|
} catch (e) {
|
|
showStatus("❌ 청크 오류 플래그 삭제 실패: " + e.message, "error");
|
|
}
|
|
}
|
|
|
|
async function clearCaches() {
|
|
try {
|
|
if ("caches" in window) {
|
|
const cacheNames = await caches.keys();
|
|
await Promise.all(cacheNames.map((name) => caches.delete(name)));
|
|
showStatus(
|
|
`✅ ${cacheNames.length}개의 캐시가 삭제되었습니다.`,
|
|
"success-msg"
|
|
);
|
|
} else {
|
|
showStatus(
|
|
"❌ 브라우저에서 캐시 API를 지원하지 않습니다.",
|
|
"error"
|
|
);
|
|
}
|
|
} catch (e) {
|
|
showStatus("❌ 캐시 삭제 실패: " + e.message, "error");
|
|
}
|
|
}
|
|
|
|
async function clearServiceWorkers() {
|
|
try {
|
|
if ("serviceWorker" in navigator) {
|
|
const registrations =
|
|
await navigator.serviceWorker.getRegistrations();
|
|
await Promise.all(registrations.map((reg) => reg.unregister()));
|
|
showStatus(
|
|
`✅ ${registrations.length}개의 서비스 워커가 삭제되었습니다.`,
|
|
"success-msg"
|
|
);
|
|
} else {
|
|
showStatus(
|
|
"❌ 브라우저에서 서비스 워커를 지원하지 않습니다.",
|
|
"error"
|
|
);
|
|
}
|
|
} catch (e) {
|
|
showStatus("❌ 서비스 워커 삭제 실패: " + e.message, "error");
|
|
}
|
|
}
|
|
|
|
function goToApp() {
|
|
const timestamp = Date.now();
|
|
window.location.href = `http://localhost:3001/?t=${timestamp}`;
|
|
}
|
|
|
|
function goToAppNoChunkHandler() {
|
|
sessionStorage.setItem("disableChunkHandler", "true");
|
|
const timestamp = Date.now();
|
|
window.location.href = `http://localhost:3001/?noChunkHandler=true&t=${timestamp}`;
|
|
}
|
|
|
|
async function emergencyReset() {
|
|
const confirmed = confirm(
|
|
"모든 저장소, 캐시, 서비스 워커를 삭제하고 앱으로 이동하시겠습니까?"
|
|
);
|
|
if (!confirmed) return;
|
|
|
|
try {
|
|
// 1. 저장소 초기화
|
|
sessionStorage.clear();
|
|
localStorage.clear();
|
|
|
|
// 2. 캐시 삭제
|
|
if ("caches" in window) {
|
|
const cacheNames = await caches.keys();
|
|
await Promise.all(cacheNames.map((name) => caches.delete(name)));
|
|
}
|
|
|
|
// 3. 서비스 워커 삭제
|
|
if ("serviceWorker" in navigator) {
|
|
const registrations =
|
|
await navigator.serviceWorker.getRegistrations();
|
|
await Promise.all(registrations.map((reg) => reg.unregister()));
|
|
}
|
|
|
|
showStatus(
|
|
"✅ 완전 초기화 완료. 3초 후 앱으로 이동합니다...",
|
|
"success-msg"
|
|
);
|
|
|
|
setTimeout(() => {
|
|
const timestamp = Date.now();
|
|
window.location.href = `http://localhost:3001/?emergency=true&t=${timestamp}`;
|
|
}, 3000);
|
|
} catch (e) {
|
|
showStatus("❌ 긴급 초기화 실패: " + e.message, "error");
|
|
}
|
|
}
|
|
|
|
function checkStatus() {
|
|
const status = {
|
|
sessionStorage: Object.keys(sessionStorage).length,
|
|
localStorage: Object.keys(localStorage).length,
|
|
chunkErrors: {
|
|
lastErrorTime: sessionStorage.getItem("lastChunkErrorTime"),
|
|
refreshCount: sessionStorage.getItem("chunkRefreshCount"),
|
|
maxRetries: sessionStorage.getItem("chunkLoadErrorMaxRetries"),
|
|
skipClerk: sessionStorage.getItem("skipClerk"),
|
|
disableClerk: sessionStorage.getItem("disableClerk"),
|
|
},
|
|
userAgent: navigator.userAgent.substring(0, 50) + "...",
|
|
timestamp: new Date().toLocaleString(),
|
|
};
|
|
|
|
document.getElementById("statusInfo").innerHTML = `
|
|
<div class="status info">
|
|
<strong>현재 상태 (${status.timestamp}):</strong><br>
|
|
• SessionStorage 항목: ${status.sessionStorage}개<br>
|
|
• LocalStorage 항목: ${status.localStorage}개<br>
|
|
• 마지막 청크 오류: ${status.chunkErrors.lastErrorTime || "없음"}<br>
|
|
• 새로고침 횟수: ${status.chunkErrors.refreshCount || "0"}<br>
|
|
• 최대 재시도 초과: ${status.chunkErrors.maxRetries || "아니오"}<br>
|
|
• Clerk 스킵: ${status.chunkErrors.skipClerk || "아니오"}<br>
|
|
• Clerk 비활성화: ${status.chunkErrors.disableClerk || "아니오"}<br>
|
|
• 브라우저: ${status.userAgent}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// 페이지 로드 시 자동으로 상태 확인
|
|
window.onload = () => {
|
|
checkStatus();
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|