From af51ba2d52fed0f8111a1939f4898325c4701cc6 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 5 Apr 2025 05:04:27 +0000 Subject: [PATCH] Fix: App crashing on launch Investigate and resolve the issue causing the application to crash during startup. --- src/components/AppVersionInfo.tsx | 1 + .../budget/hooks/useBudgetDataLoad.ts | 19 +- .../budget/hooks/useTransactionState.ts | 64 +++--- src/hooks/useDataInitialization.ts | 113 ++++++----- src/pages/Index.tsx | 187 ++++++++++-------- 5 files changed, 220 insertions(+), 164 deletions(-) diff --git a/src/components/AppVersionInfo.tsx b/src/components/AppVersionInfo.tsx index 320d149..16bbebd 100644 --- a/src/components/AppVersionInfo.tsx +++ b/src/components/AppVersionInfo.tsx @@ -1,3 +1,4 @@ + import React, { useEffect, useState } from 'react'; import { isAndroidPlatform, isIOSPlatform } from '@/utils/platform'; import { Label } from '@/components/ui/label'; diff --git a/src/contexts/budget/hooks/useBudgetDataLoad.ts b/src/contexts/budget/hooks/useBudgetDataLoad.ts index 5245cb4..ad59ee7 100644 --- a/src/contexts/budget/hooks/useBudgetDataLoad.ts +++ b/src/contexts/budget/hooks/useBudgetDataLoad.ts @@ -21,7 +21,7 @@ export const useBudgetDataLoad = ( console.log('예산 데이터 로드 시도 중...'); // 비동기 작업을 마이크로태스크로 지연 - await new Promise(resolve => queueMicrotask(() => resolve())); + await new Promise(resolve => setTimeout(() => resolve(), 0)); // 안전하게 데이터 로드 const loadedData = safelyLoadBudgetData(); @@ -40,6 +40,8 @@ export const useBudgetDataLoad = ( } } catch (error) { console.error('예산 데이터 로드 중 오류:', error); + // 오류가 발생해도 앱을 사용할 수 있도록 기본 데이터 설정 + setIsInitialized(true); } }; @@ -52,12 +54,17 @@ export const useBudgetDataLoad = ( // 로드 함수 반환 (외부에서 필요할 때 호출 가능) return { loadBudgetData: async () => { - const loadedData = safelyLoadBudgetData(); - if (JSON.stringify(loadedData) !== JSON.stringify(budgetData)) { - setBudgetData(loadedData); - setLastUpdateTime(Date.now()); + try { + const loadedData = safelyLoadBudgetData(); + if (JSON.stringify(loadedData) !== JSON.stringify(budgetData)) { + setBudgetData(loadedData); + setLastUpdateTime(Date.now()); + } + return loadedData; + } catch (error) { + console.error('loadBudgetData 호출 중 오류:', error); + return budgetData; // 오류 발생 시 현재 데이터 유지 } - return loadedData; } }; }; diff --git a/src/contexts/budget/hooks/useTransactionState.ts b/src/contexts/budget/hooks/useTransactionState.ts index 9f68e7b..a613ac9 100644 --- a/src/contexts/budget/hooks/useTransactionState.ts +++ b/src/contexts/budget/hooks/useTransactionState.ts @@ -16,8 +16,8 @@ export const useTransactionState = () => { useEffect(() => { try { const storedTransactions = loadTransactionsFromStorage(); - console.log('로컬 스토리지에서 트랜잭션 로드:', storedTransactions.length); - setTransactions(storedTransactions); + console.log('로컬 스토리지에서 트랜잭션 로드:', storedTransactions?.length || 0); + setTransactions(storedTransactions || []); } catch (error) { console.error('트랜잭션 로드 중 오류 발생:', error); // 오류 발생 시 빈 배열로 초기화 @@ -27,41 +27,57 @@ export const useTransactionState = () => { // 트랜잭션 변경 시 로컬 스토리지에 저장 useEffect(() => { - if (transactions.length > 0) { - console.log('트랜잭션 저장 중:', transactions.length); - saveTransactionsToStorage(transactions); + try { + if (transactions && transactions.length > 0) { + console.log('트랜잭션 저장 중:', transactions.length); + saveTransactionsToStorage(transactions); + } + } catch (error) { + console.error('트랜잭션 저장 중 오류 발생:', error); } }, [transactions]); // 트랜잭션 추가 const addTransaction = (transaction: Transaction) => { - const newTransaction = { - ...transaction, - id: transaction.id || uuidv4(), - localTimestamp: new Date().toISOString() - }; - setTransactions(prevTransactions => [...prevTransactions, newTransaction]); - console.log('트랜잭션 추가됨:', newTransaction); + try { + const newTransaction = { + ...transaction, + id: transaction.id || uuidv4(), + localTimestamp: new Date().toISOString() + }; + setTransactions(prevTransactions => [...(prevTransactions || []), newTransaction]); + console.log('트랜잭션 추가됨:', newTransaction); + } catch (error) { + console.error('트랜잭션 추가 중 오류 발생:', error); + } }; // 트랜잭션 업데이트 const updateTransaction = (updatedTransaction: Transaction) => { - setTransactions(prevTransactions => - prevTransactions.map(transaction => - transaction.id === updatedTransaction.id - ? { ...updatedTransaction, localTimestamp: new Date().toISOString() } - : transaction - ) - ); - console.log('트랜잭션 업데이트됨:', updatedTransaction.id); + try { + setTransactions(prevTransactions => + (prevTransactions || []).map(transaction => + transaction.id === updatedTransaction.id + ? { ...updatedTransaction, localTimestamp: new Date().toISOString() } + : transaction + ) + ); + console.log('트랜잭션 업데이트됨:', updatedTransaction.id); + } catch (error) { + console.error('트랜잭션 업데이트 중 오류 발생:', error); + } }; // 트랜잭션 삭제 const deleteTransaction = (id: string) => { - setTransactions(prevTransactions => - prevTransactions.filter(transaction => transaction.id !== id) - ); - console.log('트랜잭션 삭제됨:', id); + try { + setTransactions(prevTransactions => + (prevTransactions || []).filter(transaction => transaction.id !== id) + ); + console.log('트랜잭션 삭제됨:', id); + } catch (error) { + console.error('트랜잭션 삭제 중 오류 발생:', error); + } }; return { diff --git a/src/hooks/useDataInitialization.ts b/src/hooks/useDataInitialization.ts index b5fa740..7c5f54f 100644 --- a/src/hooks/useDataInitialization.ts +++ b/src/hooks/useDataInitialization.ts @@ -11,50 +11,56 @@ export const useDataInitialization = (resetBudgetData?: () => void) => { // 모든 데이터 초기화 함수 const initializeAllData = useCallback(async () => { - // 중요: 이미 방문한 적이 있으면 절대 초기화하지 않음 - const hasVisitedBefore = localStorage.getItem('hasVisitedBefore') === 'true'; - if (hasVisitedBefore) { - console.log('이미 앱을 방문한 적이 있으므로 데이터를 초기화하지 않습니다.'); - setIsInitialized(true); - return true; - } - - console.log('첫 방문: 모든 데이터 초기화 시작'); - - // 현재 dontShowWelcome 값 백업 - const dontShowWelcomeValue = localStorage.getItem('dontShowWelcome'); - console.log('useDataInitialization - 초기화 전 dontShowWelcome 값:', dontShowWelcomeValue); - try { - // 로그인 상태라면 클라우드 데이터도 초기화 (첫 방문 시) - if (user) { - console.log('로그인 상태: 클라우드 데이터도 초기화 시도'); - await clearCloudData(user.id); + // 중요: 이미 방문한 적이 있으면 절대 초기화하지 않음 + const hasVisitedBefore = localStorage.getItem('hasVisitedBefore') === 'true'; + if (hasVisitedBefore) { + console.log('이미 앱을 방문한 적이 있으므로 데이터를 초기화하지 않습니다.'); + setIsInitialized(true); + return true; } - // 모든 데이터 완전히 삭제 및 초기화 (한 번만 실행) - resetAllData(); - resetAllStorageData(); + console.log('첫 방문: 모든 데이터 초기화 시작'); - // 컨텍스트 데이터 리셋 (필요한 경우) - if (resetBudgetData) { - resetBudgetData(); + // 현재 dontShowWelcome 값 백업 + const dontShowWelcomeValue = localStorage.getItem('dontShowWelcome'); + console.log('useDataInitialization - 초기화 전 dontShowWelcome 값:', dontShowWelcomeValue); + + try { + // 로그인 상태라면 클라우드 데이터도 초기화 (첫 방문 시) + if (user) { + console.log('로그인 상태: 클라우드 데이터도 초기화 시도'); + await clearCloudData(user.id); + } + + // 모든 데이터 완전히 삭제 및 초기화 (한 번만 실행) + resetAllData(); + resetAllStorageData(); + + // 컨텍스트 데이터 리셋 (필요한 경우) + if (resetBudgetData) { + resetBudgetData(); + } + + // 초기화 후 dontShowWelcome 값 확인 + const afterResetValue = localStorage.getItem('dontShowWelcome'); + console.log('useDataInitialization - 초기화 후 dontShowWelcome 값:', afterResetValue); + + // 값이 유지되지 않았다면 복원 + if (dontShowWelcomeValue && afterResetValue !== dontShowWelcomeValue) { + console.log('useDataInitialization - dontShowWelcome 값 복원:', dontShowWelcomeValue); + localStorage.setItem('dontShowWelcome', dontShowWelcomeValue); + } + + console.log('모든 데이터 초기화 완료'); + return true; + } catch (error) { + console.error('데이터 초기화 중 오류 발생:', error); + return false; } - - // 초기화 후 dontShowWelcome 값 확인 - const afterResetValue = localStorage.getItem('dontShowWelcome'); - console.log('useDataInitialization - 초기화 후 dontShowWelcome 값:', afterResetValue); - - // 값이 유지되지 않았다면 복원 - if (dontShowWelcomeValue && afterResetValue !== dontShowWelcomeValue) { - console.log('useDataInitialization - dontShowWelcome 값 복원:', dontShowWelcomeValue); - localStorage.setItem('dontShowWelcome', dontShowWelcomeValue); - } - - console.log('모든 데이터 초기화 완료'); - return true; } catch (error) { - console.error('데이터 초기화 중 오류 발생:', error); + console.error('initializeAllData 함수 실행 중 오류:', error); + setIsInitialized(true); // 오류가 발생해도 앱을 사용할 수 있도록 초기화 완료로 설정 return false; } }, [resetBudgetData, user]); @@ -102,21 +108,26 @@ export const useDataInitialization = (resetBudgetData?: () => void) => { // 데이터 초기화 실행 - 첫 방문시에만 useEffect(() => { - if (!isInitialized) { - // 이미 방문한 적이 있는지 체크 (이미 있다면 초기화하지 않음) - const hasVisitedBefore = localStorage.getItem('hasVisitedBefore') === 'true'; - if (hasVisitedBefore) { - console.log('이미 방문 기록이 있어 초기화를 건너뜁니다.'); - setIsInitialized(true); - } else { - initializeAllData().then(result => { - setIsInitialized(result); - }); + try { + if (!isInitialized) { + // 이미 방문한 적이 있는지 체크 (이미 있다면 초기화하지 않음) + const hasVisitedBefore = localStorage.getItem('hasVisitedBefore') === 'true'; + if (hasVisitedBefore) { + console.log('이미 방문 기록이 있어 초기화를 건너뜁니다.'); + setIsInitialized(true); + } else { + initializeAllData().then(result => { + setIsInitialized(result); + }); + } } + + // 첫 방문 여부 체크용 키 설정 (항상 true로 설정) + localStorage.setItem('hasVisitedBefore', 'true'); + } catch (error) { + console.error('데이터 초기화 useEffect 내 오류:', error); + setIsInitialized(true); // 오류 발생해도 앱을 사용할 수 있도록 초기화 완료로 설정 } - - // 첫 방문 여부 체크용 키 설정 (항상 true로 설정) - localStorage.setItem('hasVisitedBefore', 'true'); }, [isInitialized, initializeAllData]); return { diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 106a23e..30acee6 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,3 +1,4 @@ + import React, { useEffect } from 'react'; import NavBar from '@/components/NavBar'; import AddTransactionButton from '@/components/AddTransactionButton'; @@ -41,99 +42,111 @@ const Index = () => { // 앱 시작시 예시 알림 추가 (실제 앱에서는 필요한 이벤트에 따라 알림 추가) useEffect(() => { - // 환영 메시지가 이미 표시되었는지 확인하는 키 - const welcomeNotificationSent = sessionStorage.getItem('welcomeNotificationSent'); - - if (isInitialized && user && !welcomeNotificationSent) { - // 사용자 로그인 시 알림 예시 (한 번만 실행) - const timeoutId = setTimeout(() => { - addNotification( - '환영합니다!', - '젤리의 적자탈출에 오신 것을 환영합니다. 예산을 설정하고 지출을 기록해보세요.' - ); - // 세션 스토리지에 환영 메시지 표시 여부 저장 - sessionStorage.setItem('welcomeNotificationSent', 'true'); - }, 2000); + try { + // 환영 메시지가 이미 표시되었는지 확인하는 키 + const welcomeNotificationSent = sessionStorage.getItem('welcomeNotificationSent'); - return () => clearTimeout(timeoutId); + if (isInitialized && user && !welcomeNotificationSent) { + // 사용자 로그인 시 알림 예시 (한 번만 실행) + const timeoutId = setTimeout(() => { + addNotification( + '환영합니다!', + '젤리의 적자탈출에 오신 것을 환영합니다. 예산을 설정하고 지출을 기록해보세요.' + ); + // 세션 스토리지에 환영 메시지 표시 여부 저장 + sessionStorage.setItem('welcomeNotificationSent', 'true'); + }, 2000); + + return () => clearTimeout(timeoutId); + } + } catch (error) { + console.error('환영 메시지 알림 표시 중 오류:', error); } }, [isInitialized, user, addNotification]); - // 페이지가 처음 로드될 때 데이터 로딩 확인 + // 페이지가 처음 로드될 때 데이터 로딩 확인 - 에러 방지를 위해 try/catch 추가 useEffect(() => { - console.log('Index 페이지 마운트, 현재 데이터 상태:'); - console.log('트랜잭션:', transactions.length); - console.log('예산 데이터:', budgetData); - - // 페이지 첫 마운트 시에만 실행되는 로직으로 수정 - const isFirstMount = sessionStorage.getItem('initialDataLoaded') !== 'true'; - - if (isFirstMount) { - try { - // 백업된 데이터 복구 확인 (메인 데이터가 없는 경우만) - if (!localStorage.getItem('budgetData')) { - const budgetBackup = localStorage.getItem('budgetData_backup'); - if (budgetBackup) { - console.log('예산 데이터 백업에서 복구'); - localStorage.setItem('budgetData', budgetBackup); + try { + console.log('Index 페이지 마운트, 현재 데이터 상태:'); + console.log('트랜잭션:', transactions?.length || 0); + console.log('예산 데이터:', budgetData); + + // 페이지 첫 마운트 시에만 실행되는 로직으로 수정 + const isFirstMount = sessionStorage.getItem('initialDataLoaded') !== 'true'; + + if (isFirstMount) { + try { + // 백업된 데이터 복구 확인 (메인 데이터가 없는 경우만) + if (!localStorage.getItem('budgetData')) { + const budgetBackup = localStorage.getItem('budgetData_backup'); + if (budgetBackup) { + console.log('예산 데이터 백업에서 복구'); + localStorage.setItem('budgetData', budgetBackup); + } } - } - - if (!localStorage.getItem('categoryBudgets')) { - const categoryBackup = localStorage.getItem('categoryBudgets_backup'); - if (categoryBackup) { - console.log('카테고리 예산 백업에서 복구'); - localStorage.setItem('categoryBudgets', categoryBackup); + + if (!localStorage.getItem('categoryBudgets')) { + const categoryBackup = localStorage.getItem('categoryBudgets_backup'); + if (categoryBackup) { + console.log('카테고리 예산 백업에서 복구'); + localStorage.setItem('categoryBudgets', categoryBackup); + } } - } - - if (!localStorage.getItem('transactions')) { - const transactionBackup = localStorage.getItem('transactions_backup'); - if (transactionBackup) { - console.log('트랜잭션 백업에서 복구'); - localStorage.setItem('transactions', transactionBackup); + + if (!localStorage.getItem('transactions')) { + const transactionBackup = localStorage.getItem('transactions_backup'); + if (transactionBackup) { + console.log('트랜잭션 백업에서 복구'); + localStorage.setItem('transactions', transactionBackup); + } } + + // 한 번만 이벤트 발생 + window.dispatchEvent(new Event('transactionUpdated')); + window.dispatchEvent(new Event('budgetDataUpdated')); + window.dispatchEvent(new Event('categoryBudgetsUpdated')); + + // 초기 로드 완료 표시 + sessionStorage.setItem('initialDataLoaded', 'true'); + } catch (error) { + console.error('백업 복구 시도 중 오류:', error); } - - // 한 번만 이벤트 발생 - window.dispatchEvent(new Event('transactionUpdated')); - window.dispatchEvent(new Event('budgetDataUpdated')); - window.dispatchEvent(new Event('categoryBudgetsUpdated')); - - // 초기 로드 완료 표시 - sessionStorage.setItem('initialDataLoaded', 'true'); - } catch (error) { - console.error('백업 복구 시도 중 오류:', error); } + } catch (error) { + console.error('Index 페이지 초기화 중 오류:', error); } }, []); // 의존성 배열 비움 - 컴포넌트 마운트 시 한 번만 실행 // 앱이 포커스를 얻었을 때 데이터를 새로고침 useEffect(() => { const handleFocus = () => { - console.log('창이 포커스를 얻음 - 데이터 새로고침'); - // 이미 리프레시 중인지 확인하는 플래그 - if (sessionStorage.getItem('isRefreshing') === 'true') { - console.log('이미 리프레시 진행 중, 중복 실행 방지'); - return; - } - try { - sessionStorage.setItem('isRefreshing', 'true'); + console.log('창이 포커스를 얻음 - 데이터 새로고침'); + // 이미 리프레시 중인지 확인하는 플래그 + if (sessionStorage.getItem('isRefreshing') === 'true') { + console.log('이미 리프레시 진행 중, 중복 실행 방지'); + return; + } - // 이벤트 발생시켜 데이터 새로고침 - window.dispatchEvent(new Event('storage')); - window.dispatchEvent(new Event('transactionUpdated')); - window.dispatchEvent(new Event('budgetDataUpdated')); - window.dispatchEvent(new Event('categoryBudgetsUpdated')); - - // 리프레시 완료 표시 (300ms 후에 플래그 해제) - setTimeout(() => { + try { + sessionStorage.setItem('isRefreshing', 'true'); + + // 이벤트 발생시켜 데이터 새로고침 + window.dispatchEvent(new Event('storage')); + window.dispatchEvent(new Event('transactionUpdated')); + window.dispatchEvent(new Event('budgetDataUpdated')); + window.dispatchEvent(new Event('categoryBudgetsUpdated')); + + // 리프레시 완료 표시 (300ms 후에 플래그 해제) + setTimeout(() => { + sessionStorage.setItem('isRefreshing', 'false'); + }, 300); + } catch (e) { + console.error('이벤트 발생 오류:', e); sessionStorage.setItem('isRefreshing', 'false'); - }, 300); - } catch (e) { - console.error('이벤트 발생 오류:', e); - sessionStorage.setItem('isRefreshing', 'false'); + } + } catch (error) { + console.error('포커스 이벤트 처리 중 오류:', error); } }; @@ -142,9 +155,13 @@ const Index = () => { // 가시성 변경 이벤트 (백그라운드에서 전경으로 돌아올 때) const handleVisibilityChange = () => { - if (document.visibilityState === 'visible') { - console.log('페이지가 다시 보임 - 데이터 새로고침'); - handleFocus(); + try { + if (document.visibilityState === 'visible') { + console.log('페이지가 다시 보임 - 데이터 새로고침'); + handleFocus(); + } + } catch (error) { + console.error('가시성 이벤트 처리 중 오류:', error); } }; @@ -152,10 +169,14 @@ const Index = () => { // 정기적인 데이터 새로고침 (60초마다로 변경 - 너무 빈번한 리프레시 방지) const refreshInterval = setInterval(() => { - if (document.visibilityState === 'visible' && - sessionStorage.getItem('isRefreshing') !== 'true') { - console.log('정기 새로고침 - 데이터 업데이트'); - handleFocus(); + try { + if (document.visibilityState === 'visible' && + sessionStorage.getItem('isRefreshing') !== 'true') { + console.log('정기 새로고침 - 데이터 업데이트'); + handleFocus(); + } + } catch (error) { + console.error('정기 새로고침 처리 중 오류:', error); } }, 60000); // 10초에서 60초로 변경 @@ -172,8 +193,8 @@ const Index = () => {