Files
zellyy-finance/emergency-reset.html
hansoo 8343b25439 feat: Stage 2 TypeScript 타입 안전성 개선 - any 타입 83개 → 62개 대폭 감소
 주요 개선사항:
- 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>
2025-07-14 10:08:51 +09:00

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>