주요 변경사항: • Appwrite SDK 및 관련 의존성 완전 제거 • Lovable 관련 도구 및 설정 제거 • 기존 Appwrite 기반 컴포넌트 및 훅 삭제 • Login/Register 페이지를 Clerk 기반으로 완전 전환 제거된 구성요소: • src/lib/appwrite/ - 전체 디렉토리 • src/contexts/auth/ - 기존 인증 컨텍스트 • 구형 auth 컴포넌트들 (RegisterForm, LoginForm 등) • useAuthQueries, useTransactionQueries 훅 • Appwrite 기반 테스트 파일들 설정 변경: • package.json - appwrite, lovable-tagger 의존성 제거 • .env 파일 - Appwrite 환경변수 제거 • vercel.json - Supabase/Clerk 환경변수로 교체 • vite.config.ts - 청크 분할 설정 업데이트 성능 개선: • 번들 크기 최적화 (Appwrite → Clerk + Supabase) • 불필요한 코드 및 타입 정의 제거 • 테스트 설정을 Clerk/Supabase 모킹으로 업데이트 Task 11.4 완료: 기존 Appwrite 코드 완전 제거 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
315 lines
8.8 KiB
TypeScript
315 lines
8.8 KiB
TypeScript
/**
|
|
* 동기화 관련 React Query 훅들
|
|
*
|
|
* 기존 동기화 로직을 React Query로 전환하여
|
|
* 백그라운드 동기화와 상태 관리를 최적화합니다.
|
|
*/
|
|
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import {
|
|
trySyncAllData,
|
|
getLastSyncTime,
|
|
setLastSyncTime,
|
|
} from "@/utils/syncUtils";
|
|
import {
|
|
queryKeys,
|
|
queryConfigs,
|
|
handleQueryError,
|
|
invalidateQueries,
|
|
} from "@/lib/query/queryClient";
|
|
import { syncLogger } from "@/utils/logger";
|
|
import { useAuthStore } from "@/stores";
|
|
import { toast } from "@/hooks/useToast.wrapper";
|
|
import useNotifications from "@/hooks/useNotifications";
|
|
|
|
/**
|
|
* 마지막 동기화 시간 조회 쿼리
|
|
*/
|
|
export const useLastSyncTimeQuery = () => {
|
|
return useQuery({
|
|
queryKey: queryKeys.sync.lastSync(),
|
|
queryFn: async () => {
|
|
const lastSyncTime = getLastSyncTime();
|
|
syncLogger.info("마지막 동기화 시간 조회", { lastSyncTime });
|
|
return lastSyncTime;
|
|
},
|
|
staleTime: 30 * 1000, // 30초
|
|
gcTime: 5 * 60 * 1000, // 5분
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 동기화 상태 조회 쿼리
|
|
*/
|
|
export const useSyncStatusQuery = () => {
|
|
const { user } = useAuthStore();
|
|
|
|
return useQuery({
|
|
queryKey: queryKeys.sync.status(),
|
|
queryFn: async () => {
|
|
if (!user?.id) {
|
|
return {
|
|
canSync: false,
|
|
reason: "사용자 인증이 필요합니다.",
|
|
lastSyncTime: null,
|
|
};
|
|
}
|
|
|
|
const lastSyncTime = getLastSyncTime();
|
|
const now = new Date();
|
|
const lastSync = lastSyncTime ? new Date(lastSyncTime) : null;
|
|
|
|
// 마지막 동기화로부터 얼마나 시간이 지났는지 계산
|
|
const timeSinceLastSync = lastSync
|
|
? Math.floor((now.getTime() - lastSync.getTime()) / 1000 / 60) // 분 단위
|
|
: null;
|
|
|
|
return {
|
|
canSync: true,
|
|
reason: null,
|
|
lastSyncTime,
|
|
timeSinceLastSync,
|
|
needsSync: !lastSync || timeSinceLastSync > 30, // 30분 이상 지났으면 동기화 필요
|
|
};
|
|
},
|
|
...queryConfigs.userInfo,
|
|
enabled: !!user?.id,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 수동 동기화 뮤테이션
|
|
*
|
|
* - 사용자가 수동으로 동기화를 트리거할 때 사용
|
|
* - 성공 시 모든 관련 쿼리 무효화
|
|
* - 알림 및 토스트 메시지 제공
|
|
*/
|
|
export const useManualSyncMutation = () => {
|
|
const queryClient = useQueryClient();
|
|
const { user } = useAuthStore();
|
|
const { addNotification } = useNotifications();
|
|
|
|
return useMutation({
|
|
mutationFn: async (): Promise<any> => {
|
|
if (!user?.id) {
|
|
throw new Error("사용자 인증이 필요합니다.");
|
|
}
|
|
|
|
syncLogger.info("수동 동기화 뮤테이션 시작", { userId: user.id });
|
|
|
|
// 동기화 실행
|
|
const result = await trySyncAllData(user.id);
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || "동기화에 실패했습니다.");
|
|
}
|
|
|
|
// 동기화 시간 업데이트
|
|
const currentTime = new Date().toISOString();
|
|
setLastSyncTime(currentTime);
|
|
|
|
syncLogger.info("수동 동기화 성공", {
|
|
syncTime: currentTime,
|
|
result,
|
|
});
|
|
|
|
return { ...result, syncTime: currentTime };
|
|
},
|
|
|
|
// 뮤테이션 시작 시
|
|
onMutate: () => {
|
|
syncLogger.info("동기화 시작 알림");
|
|
addNotification("동기화 시작", "데이터 동기화가 시작되었습니다.");
|
|
},
|
|
|
|
// 성공 시 처리
|
|
onSuccess: (result) => {
|
|
// 모든 쿼리 무효화하여 최신 데이터 로드
|
|
invalidateQueries.all();
|
|
|
|
// 동기화 관련 쿼리 즉시 업데이트
|
|
queryClient.setQueryData(queryKeys.sync.lastSync(), result.syncTime);
|
|
|
|
// 성공 알림
|
|
toast({
|
|
title: "동기화 완료",
|
|
description: "모든 데이터가 성공적으로 동기화되었습니다.",
|
|
});
|
|
|
|
addNotification(
|
|
"동기화 완료",
|
|
"모든 데이터가 성공적으로 동기화되었습니다."
|
|
);
|
|
|
|
syncLogger.info("수동 동기화 뮤테이션 성공 완료", result);
|
|
},
|
|
|
|
// 에러 시 처리
|
|
onError: (error: any) => {
|
|
const friendlyMessage = handleQueryError(error, "동기화");
|
|
syncLogger.error("수동 동기화 뮤테이션 실패:", friendlyMessage);
|
|
|
|
toast({
|
|
title: "동기화 실패",
|
|
description: friendlyMessage,
|
|
variant: "destructive",
|
|
});
|
|
|
|
addNotification("동기화 실패", friendlyMessage);
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 백그라운드 자동 동기화 뮤테이션
|
|
*
|
|
* - 조용한 동기화 (알림 없음)
|
|
* - 에러 시에도 사용자를 방해하지 않음
|
|
* - 성공 시에만 데이터 업데이트
|
|
*/
|
|
export const useBackgroundSyncMutation = () => {
|
|
const queryClient = useQueryClient();
|
|
const { user } = useAuthStore();
|
|
|
|
return useMutation({
|
|
mutationFn: async (): Promise<any> => {
|
|
if (!user?.id) {
|
|
throw new Error("사용자 인증이 필요합니다.");
|
|
}
|
|
|
|
syncLogger.info("백그라운드 동기화 시작", { userId: user.id });
|
|
|
|
const result = await trySyncAllData(user.id);
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.error || "백그라운드 동기화에 실패했습니다.");
|
|
}
|
|
|
|
const currentTime = new Date().toISOString();
|
|
setLastSyncTime(currentTime);
|
|
|
|
syncLogger.info("백그라운드 동기화 성공", {
|
|
syncTime: currentTime,
|
|
});
|
|
|
|
return { ...result, syncTime: currentTime };
|
|
},
|
|
|
|
// 성공 시 조용히 데이터 업데이트
|
|
onSuccess: (result) => {
|
|
// 트랜잭션과 예산 데이터만 조용히 업데이트
|
|
invalidateQueries.transactions();
|
|
invalidateQueries.budget();
|
|
|
|
// 동기화 시간 업데이트
|
|
queryClient.setQueryData(queryKeys.sync.lastSync(), result.syncTime);
|
|
|
|
syncLogger.info("백그라운드 동기화 완료 - 데이터 업데이트됨");
|
|
},
|
|
|
|
// 에러 시 조용히 로그만 남김
|
|
onError: (error: any) => {
|
|
syncLogger.warn(
|
|
"백그라운드 동기화 실패 (조용히 처리됨):",
|
|
error?.message
|
|
);
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 자동 동기화 간격 설정을 위한 쿼리
|
|
*
|
|
* - 설정된 간격에 따라 백그라운드 동기화 실행
|
|
* - 네트워크 상태에 따른 동적 조정
|
|
*/
|
|
export const useAutoSyncQuery = (intervalMinutes = 5) => {
|
|
const { user } = useAuthStore();
|
|
const backgroundSyncMutation = useBackgroundSyncMutation();
|
|
|
|
return useQuery({
|
|
queryKey: ["auto-sync", intervalMinutes],
|
|
queryFn: async () => {
|
|
if (!user?.id) {
|
|
return null;
|
|
}
|
|
|
|
// 백그라운드 동기화 실행
|
|
if (!backgroundSyncMutation.isPending) {
|
|
backgroundSyncMutation.mutate();
|
|
}
|
|
|
|
return new Date().toISOString();
|
|
},
|
|
enabled: !!user?.id,
|
|
refetchInterval: intervalMinutes * 60 * 1000, // 분을 밀리초로 변환
|
|
refetchIntervalInBackground: false, // 백그라운드에서는 실행하지 않음
|
|
staleTime: Infinity, // 항상 최신으로 유지
|
|
gcTime: 0, // 캐시하지 않음
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 통합 동기화 훅 (기존 useManualSync와 호환성 유지)
|
|
*
|
|
* React Query 뮤테이션과 기존 인터페이스를 결합
|
|
*/
|
|
export const useSync = () => {
|
|
const { user } = useAuthStore();
|
|
const lastSyncQuery = useLastSyncTimeQuery();
|
|
const syncStatusQuery = useSyncStatusQuery();
|
|
const manualSyncMutation = useManualSyncMutation();
|
|
const backgroundSyncMutation = useBackgroundSyncMutation();
|
|
|
|
return {
|
|
// 동기화 상태
|
|
lastSyncTime: lastSyncQuery.data,
|
|
syncStatus: syncStatusQuery.data,
|
|
|
|
// 수동 동기화 (기존 handleManualSync와 동일한 인터페이스)
|
|
syncing: manualSyncMutation.isPending,
|
|
handleManualSync: manualSyncMutation.mutate,
|
|
|
|
// 백그라운드 동기화
|
|
backgroundSyncing: backgroundSyncMutation.isPending,
|
|
triggerBackgroundSync: backgroundSyncMutation.mutate,
|
|
|
|
// 동기화 가능 여부
|
|
canSync: !!user?.id && syncStatusQuery.data?.canSync,
|
|
|
|
// 쿼리 제어
|
|
refetchSyncStatus: syncStatusQuery.refetch,
|
|
refetchLastSyncTime: lastSyncQuery.refetch,
|
|
};
|
|
};
|
|
|
|
/**
|
|
* 동기화 설정 관리 훅
|
|
*/
|
|
export const useSyncSettings = () => {
|
|
const queryClient = useQueryClient();
|
|
|
|
// 자동 동기화 간격 설정 (localStorage 기반)
|
|
const setAutoSyncInterval = (intervalMinutes: number) => {
|
|
localStorage.setItem("auto-sync-interval", intervalMinutes.toString());
|
|
|
|
// 관련 쿼리 무효화
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["auto-sync"],
|
|
});
|
|
|
|
syncLogger.info("자동 동기화 간격 설정됨", { intervalMinutes });
|
|
};
|
|
|
|
const getAutoSyncInterval = (): number => {
|
|
const stored = localStorage.getItem("auto-sync-interval");
|
|
return stored ? parseInt(stored, 10) : 5; // 기본값 5분
|
|
};
|
|
|
|
return {
|
|
setAutoSyncInterval,
|
|
getAutoSyncInterval,
|
|
currentInterval: getAutoSyncInterval(),
|
|
};
|
|
};
|