Refactor SyncSettings component

Refactor SyncSettings component into smaller components and extract logic into a separate hook.
This commit is contained in:
gpt-engineer-app[bot]
2025-03-16 10:40:07 +00:00
parent 14c3fac824
commit 1d911153a7
4 changed files with 282 additions and 208 deletions

View File

@@ -1,180 +1,25 @@
import React, { useState, useEffect } from 'react';
import React from 'react';
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { CloudUpload, RefreshCw, AlertCircle } from "lucide-react";
import { isSyncEnabled, setSyncEnabled, syncAllData, getLastSyncTime, trySyncAllData, SyncResult } from "@/utils/syncUtils";
import { toast } from "@/hooks/useToast.wrapper";
import { useAuth } from "@/contexts/auth";
import { useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { CloudUpload } from "lucide-react";
import { useSyncSettings } from '@/hooks/useSyncSettings';
import SyncStatus from '@/components/sync/SyncStatus';
import SyncExplanation from '@/components/sync/SyncExplanation';
const SyncSettings = () => {
const [enabled, setEnabled] = useState(isSyncEnabled());
const [syncing, setSyncing] = useState(false);
const [lastSync, setLastSync] = useState<string | null>(getLastSyncTime());
const { user } = useAuth();
const navigate = useNavigate();
// 사용자 로그인 상태 변경 감지
useEffect(() => {
// 사용자 로그인 상태에 따라 동기화 설정 업데이트
const updateSyncState = () => {
if (!user && isSyncEnabled()) {
// 사용자가 로그아웃했고 동기화가 활성화되어 있으면 비활성화
setSyncEnabled(false);
setEnabled(false);
setLastSync(null);
console.log('로그아웃으로 인해 동기화 설정이 비활성화되었습니다.');
}
// 동기화 상태 업데이트
setEnabled(isSyncEnabled());
setLastSync(getLastSyncTime());
};
// 초기 호출
updateSyncState();
// 인증 상태 변경 이벤트 리스너
window.addEventListener('auth-state-changed', updateSyncState);
return () => {
window.removeEventListener('auth-state-changed', updateSyncState);
};
}, [user]);
// 마지막 동기화 시간 정기적으로 업데이트
useEffect(() => {
const intervalId = setInterval(() => {
setLastSync(getLastSyncTime());
}, 10000); // 10초마다 업데이트
return () => clearInterval(intervalId);
}, []);
const handleSyncToggle = async (checked: boolean) => {
if (!user && checked) {
toast({
title: "로그인 필요",
description: "데이터 동기화를 위해 로그인이 필요합니다.",
variant: "destructive"
});
return;
}
setEnabled(checked);
setSyncEnabled(checked);
if (checked && user) {
// 동기화 활성화 시 즉시 동기화 실행
try {
setSyncing(true);
// 안전한 동기화 함수 사용
const result = await trySyncAllData(user.id);
if (result.success) {
if (result.partial) {
toast({
title: "동기화 일부 완료",
description: "일부 데이터만 동기화되었습니다. 다시 시도해보세요.",
variant: "destructive"
});
} else {
toast({
title: "동기화 설정 완료",
description: "모든 데이터가 클라우드에 동기화되었습니다.",
});
}
setLastSync(getLastSyncTime());
} else {
toast({
title: "동기화 일부 완료",
description: "일부 데이터가 동기화되지 않았습니다. 나중에 다시 시도해주세요.",
variant: "destructive"
});
}
} catch (error) {
toast({
title: "동기화 오류",
description: "동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
variant: "destructive"
});
// 실패 시 동기화 비활성화
setEnabled(false);
setSyncEnabled(false);
} finally {
setSyncing(false);
}
}
};
const handleManualSync = async () => {
if (!user) {
toast({
title: "로그인 필요",
description: "데이터 동기화를 위해 로그인이 필요합니다.",
variant: "destructive"
});
return;
}
try {
setSyncing(true);
// 안전한 동기화 함수 사용
const result = await trySyncAllData(user.id);
if (result.success) {
if (result.downloadSuccess && result.uploadSuccess) {
toast({
title: "동기화 완료",
description: "모든 데이터가 클라우드에 동기화되었습니다.",
});
} else if (result.downloadSuccess) {
toast({
title: "다운로드만 성공",
description: "서버 데이터를 가져왔지만, 업로드에 실패했습니다.",
variant: "destructive"
});
} else if (result.uploadSuccess) {
toast({
title: "업로드만 성공",
description: "로컬 데이터를 업로드했지만, 다운로드에 실패했습니다.",
variant: "destructive"
});
}
setLastSync(getLastSyncTime());
} else {
toast({
title: "일부 동기화 실패",
description: "일부 데이터 동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
variant: "destructive"
});
}
} catch (error) {
console.error('동기화 오류:', error);
toast({
title: "동기화 오류",
description: "동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
variant: "destructive"
});
} finally {
setSyncing(false);
}
};
const formatLastSyncTime = () => {
if (!lastSync) return "아직 동기화된 적 없음";
if (lastSync === '부분-다운로드') return "부분 동기화 (다운로드만)";
const date = new Date(lastSync);
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
};
const {
enabled,
syncing,
user,
lastSync,
handleSyncToggle,
handleManualSync
} = useSyncSettings();
return (
<div className="space-y-6">
{/* 동기화 토글 컨트롤 */}
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<div className="flex items-center gap-2">
@@ -196,45 +41,17 @@ const SyncSettings = () => {
/>
</div>
{enabled && (
<div className="space-y-3">
{user ? (
<>
<div className="flex justify-between items-center text-sm">
<span className="text-muted-foreground"> : {formatLastSyncTime()}</span>
<button
onClick={handleManualSync}
disabled={syncing}
className="neuro-button py-1 px-3 flex items-center gap-1 bg-neuro-income text-white hover:bg-neuro-income/90"
>
<RefreshCw className={`h-4 w-4 ${syncing ? 'animate-spin' : ''}`} />
<span>{syncing ? '동기화 중...' : '지금 동기화'}</span>
</button>
</div>
<Alert className="bg-amber-50 text-amber-800 border-amber-200">
<AlertCircle className="h-4 w-4" />
<AlertTitle> </AlertTitle>
<AlertDescription className="text-sm">
. .
.
</AlertDescription>
</Alert>
</>
) : (
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground"> </span>
<Button
onClick={() => navigate('/login')}
size="sm"
className="py-1 px-3 bg-neuro-income text-white hover:bg-neuro-income/90"
>
</Button>
</div>
)}
</div>
)}
{/* 동기화 상태 및 동작 */}
<SyncStatus
enabled={enabled}
syncing={syncing}
lastSync={lastSync}
user={user}
onManualSync={handleManualSync}
/>
{/* 동기화 설명 */}
<SyncExplanation enabled={enabled} />
</div>
);
};

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react";
interface SyncExplanationProps {
enabled: boolean;
}
const SyncExplanation: React.FC<SyncExplanationProps> = ({ enabled }) => {
if (!enabled) return null;
return (
<Alert className="bg-amber-50 text-amber-800 border-amber-200">
<AlertCircle className="h-4 w-4" />
<AlertTitle> </AlertTitle>
<AlertDescription className="text-sm">
. .
.
</AlertDescription>
</Alert>
);
};
export default SyncExplanation;

View File

@@ -0,0 +1,56 @@
import React from 'react';
import { RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
import { useNavigate } from "react-router-dom";
interface SyncStatusProps {
enabled: boolean;
syncing: boolean;
lastSync: string;
user: any; // User 타입 또는 null
onManualSync: () => Promise<void>;
}
const SyncStatus: React.FC<SyncStatusProps> = ({
enabled,
syncing,
lastSync,
user,
onManualSync
}) => {
const navigate = useNavigate();
if (!enabled) return null;
return (
<div className="space-y-3">
{user ? (
<div className="flex justify-between items-center text-sm">
<span className="text-muted-foreground"> : {lastSync}</span>
<button
onClick={onManualSync}
disabled={syncing}
className="neuro-button py-1 px-3 flex items-center gap-1 bg-neuro-income text-white hover:bg-neuro-income/90"
>
<RefreshCw className={`h-4 w-4 ${syncing ? 'animate-spin' : ''}`} />
<span>{syncing ? '동기화 중...' : '지금 동기화'}</span>
</button>
</div>
) : (
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground"> </span>
<Button
onClick={() => navigate('/login')}
size="sm"
className="py-1 px-3 bg-neuro-income text-white hover:bg-neuro-income/90"
>
</Button>
</div>
)}
</div>
);
};
export default SyncStatus;

View File

@@ -0,0 +1,176 @@
import { useState, useEffect } from 'react';
import { useAuth } from '@/contexts/auth';
import { toast } from '@/hooks/useToast.wrapper';
import {
isSyncEnabled,
setSyncEnabled,
getLastSyncTime,
trySyncAllData,
SyncResult
} from '@/utils/syncUtils';
/**
* 동기화 설정 관리를 위한 커스텀 훅
*/
export const useSyncSettings = () => {
const [enabled, setEnabled] = useState(isSyncEnabled());
const [syncing, setSyncing] = useState(false);
const [lastSync, setLastSync] = useState<string | null>(getLastSyncTime());
const { user } = useAuth();
// 사용자 로그인 상태 변경 감지
useEffect(() => {
// 사용자 로그인 상태에 따라 동기화 설정 업데이트
const updateSyncState = () => {
if (!user && isSyncEnabled()) {
// 사용자가 로그아웃했고 동기화가 활성화되어 있으면 비활성화
setSyncEnabled(false);
setEnabled(false);
setLastSync(null);
console.log('로그아웃으로 인해 동기화 설정이 비활성화되었습니다.');
}
// 동기화 상태 업데이트
setEnabled(isSyncEnabled());
setLastSync(getLastSyncTime());
};
// 초기 호출
updateSyncState();
// 인증 상태 변경 이벤트 리스너
window.addEventListener('auth-state-changed', updateSyncState);
return () => {
window.removeEventListener('auth-state-changed', updateSyncState);
};
}, [user]);
// 마지막 동기화 시간 정기적으로 업데이트
useEffect(() => {
const intervalId = setInterval(() => {
setLastSync(getLastSyncTime());
}, 10000); // 10초마다 업데이트
return () => clearInterval(intervalId);
}, []);
// 동기화 토글 핸들러
const handleSyncToggle = async (checked: boolean) => {
if (!user && checked) {
toast({
title: "로그인 필요",
description: "데이터 동기화를 위해 로그인이 필요합니다.",
variant: "destructive"
});
return;
}
setEnabled(checked);
setSyncEnabled(checked);
if (checked && user) {
// 동기화 활성화 시 즉시 동기화 실행
await performSync();
}
};
// 수동 동기화 핸들러
const handleManualSync = async () => {
if (!user) {
toast({
title: "로그인 필요",
description: "데이터 동기화를 위해 로그인이 필요합니다.",
variant: "destructive"
});
return;
}
await performSync();
};
// 실제 동기화 수행 함수
const performSync = async () => {
if (!user) return;
try {
setSyncing(true);
// 안전한 동기화 함수 사용
const result = await trySyncAllData(user.id);
handleSyncResult(result);
setLastSync(getLastSyncTime());
} catch (error) {
console.error('동기화 오류:', error);
toast({
title: "동기화 오류",
description: "동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
variant: "destructive"
});
// 심각한 오류 발생 시 동기화 비활성화
if (!enabled) {
setEnabled(false);
setSyncEnabled(false);
}
} finally {
setSyncing(false);
}
};
// 동기화 결과 처리 함수
const handleSyncResult = (result: SyncResult) => {
if (result.success) {
if (result.downloadSuccess && result.uploadSuccess) {
toast({
title: "동기화 완료",
description: "모든 데이터가 클라우드에 동기화되었습니다.",
});
} else if (result.downloadSuccess) {
toast({
title: "다운로드만 성공",
description: "서버 데이터를 가져왔지만, 업로드에 실패했습니다.",
variant: "destructive"
});
} else if (result.uploadSuccess) {
toast({
title: "업로드만 성공",
description: "로컬 데이터를 업로드했지만, 다운로드에 실패했습니다.",
variant: "destructive"
});
} else if (result.partial) {
toast({
title: "동기화 일부 완료",
description: "일부 데이터만 동기화되었습니다. 다시 시도해보세요.",
variant: "destructive"
});
}
} else {
toast({
title: "일부 동기화 실패",
description: "일부 데이터 동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
variant: "destructive"
});
}
};
// 마지막 동기화 시간 포맷팅
const formatLastSyncTime = () => {
if (!lastSync) return "아직 동기화된 적 없음";
if (lastSync === '부분-다운로드') return "부분 동기화 (다운로드만)";
const date = new Date(lastSync);
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
};
return {
enabled,
syncing,
user,
lastSync: formatLastSyncTime(),
handleSyncToggle,
handleManualSync,
};
};