Skip to content

Commit ff2a074

Browse files
SOIVclaude
andcommitted
feat(web): Setup 진행 중 새로고침/재접속 복구 기능 구현
sessionStorage에 진행 상태 저장, 재접속 시 progress 단계 복원. /setup/status 폴링으로 서버 완료 감지 시 complete로 자동 이동. 복구 배너 및 재시도 버튼 표시. 로드맵 체크리스트 업데이트. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4efaad9 commit ff2a074

2 files changed

Lines changed: 87 additions & 12 deletions

File tree

apps/web/src/views/SetupWizardView.tsx

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -634,16 +634,80 @@ function CompleteStep({ onGoLogin }: { onGoLogin: () => void }) {
634634
);
635635
}
636636

637+
// ─── 새로고침 복구 ────────────────────────────────────────────────────────────
638+
639+
const RECOVERY_KEY = 'fs_setup_recovery';
640+
641+
interface RecoveryData {
642+
entries: ProgressEntry[];
643+
hasError: boolean;
644+
}
645+
646+
function saveRecovery(entries: ProgressEntry[], hasError: boolean) {
647+
try {
648+
sessionStorage.setItem(RECOVERY_KEY, JSON.stringify({ entries, hasError } satisfies RecoveryData));
649+
} catch { /* ignore */ }
650+
}
651+
652+
function loadRecovery(): RecoveryData | null {
653+
try {
654+
const raw = sessionStorage.getItem(RECOVERY_KEY);
655+
if (!raw) return null;
656+
return JSON.parse(raw) as RecoveryData;
657+
} catch { return null; }
658+
}
659+
660+
function clearRecovery() {
661+
try { sessionStorage.removeItem(RECOVERY_KEY); } catch { /* ignore */ }
662+
}
663+
637664
// ─── Root Wizard ──────────────────────────────────────────────────────────────
638665

639666
export function SetupWizardView({ onComplete }: { onComplete: () => void }) {
640-
const [step, setStep] = useState<WizardStep>("welcome");
667+
// 새로고침 시 progress 단계 복원
668+
const recovered = loadRecovery();
669+
670+
const [step, setStep] = useState<WizardStep>(recovered ? "progress" : "welcome");
641671
const [formData, setFormData] = useState<ConfigFormData | null>(null);
642-
const [progressEntries, setProgressEntries] = useState<ProgressEntry[]>([]);
643-
const [hasError, setHasError] = useState(false);
672+
const [progressEntries, setProgressEntries] = useState<ProgressEntry[]>(recovered?.entries ?? []);
673+
const [hasError, setHasError] = useState(recovered?.hasError ?? false);
674+
const [isRestored, setIsRestored] = useState(recovered !== null);
675+
676+
// 복원된 상태: /setup/status 폴링 → 서버가 이미 완료했으면 complete로 이동
677+
useEffect(() => {
678+
if (!isRestored) return;
679+
let cancelled = false;
680+
681+
const poll = async () => {
682+
for (let i = 0; i < 8 && !cancelled; i++) {
683+
try {
684+
const res = await fetch('/setup/status');
685+
const json = await res.json() as { success: boolean; data?: { installed?: boolean } };
686+
if (json.data?.installed === true) {
687+
clearRecovery();
688+
setStep('complete');
689+
return;
690+
}
691+
} catch { /* API 아직 재시작 중 */ }
692+
await new Promise<void>((r) => setTimeout(r, 1500));
693+
}
694+
// 폴링 종료 — 설치 상태 미확정: 복구 배너 유지, 유저가 재시도 가능
695+
};
696+
697+
void poll();
698+
return () => { cancelled = true; };
699+
}, [isRestored]);
700+
701+
// progress 단계의 상태가 바뀔 때마다 sessionStorage에 저장
702+
useEffect(() => {
703+
if (step === 'progress') {
704+
saveRecovery(progressEntries, hasError);
705+
}
706+
}, [step, progressEntries, hasError]);
644707

645708
const handleConfigNext = (data: ConfigFormData) => {
646709
setFormData(data);
710+
setIsRestored(false);
647711
setStep("progress");
648712
void runSetup(data);
649713
};
@@ -710,7 +774,8 @@ export function SetupWizardView({ onComplete }: { onComplete: () => void }) {
710774
}
711775
}
712776

713-
// 성공 완료 — 잠시 후 complete step
777+
// 성공 완료 — 복구 데이터 제거 후 complete step으로 이동
778+
clearRecovery();
714779
setTimeout(() => setStep("complete"), 1000);
715780
} catch (e: unknown) {
716781
setProgressEntries((prev) => [
@@ -748,16 +813,24 @@ export function SetupWizardView({ onComplete }: { onComplete: () => void }) {
748813
)}
749814
{step === "progress" && (
750815
<>
816+
{/* 새로고침/재접속 복구 배너 */}
817+
{isRestored && (
818+
<div className="setup-alert warn" style={{ marginBottom: 16 }}>
819+
<strong>재접속 감지됨</strong> — 이전 설치 진행 상태를 복원했습니다.
820+
서버가 아직 설치 중이라면 자동으로 완료 단계로 이동합니다.
821+
설치가 중단된 경우 아래 버튼으로 다시 시도할 수 있습니다.
822+
</div>
823+
)}
751824
<ProgressStep entries={progressEntries} hasError={hasError} />
752-
{hasError && (
825+
{(hasError || isRestored) && (
753826
<div className="setup-footer">
754827
<button
755828
className="setup-btn secondary"
756-
onClick={() => setStep("config")}
829+
onClick={() => { clearRecovery(); setIsRestored(false); setStep("config"); }}
757830
>
758831
← 구성으로 돌아가기
759832
</button>
760-
{formData && (
833+
{formData && !isRestored && (
761834
<button
762835
className="setup-btn primary"
763836
onClick={() => { void runSetup(formData); }}

docs/v2_FINANCIAL-LEDGER/roadmap/01-development-plan.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ Control 전체 목록과 상태 관리는 별도 문서에서 관리:
292292
- [x] DB 연결 초기화 및 연결 실패 처리 (5회 지수 백오프 재시도)
293293
- [x] 마이그레이션 러너 실제 동작 구현 (`packages/core/src/db/migrations/`) — `06-migrations.md` 설계 기준
294294
- [x] DB 프로바이더 추상화 검증 (타입체크 전체 통과, Node16 ESM 해석 이슈 수정)
295-
- [ ] SQLite Provider 구현 (경량 단독 인스턴스용 — 2순위)
295+
- [x] SQLite Provider 구현 (경량 단독 인스턴스용 — 2순위)
296296

297297
#### 1.9.3 인증 백엔드 구현
298298
**예상 기간: 1주**
@@ -384,7 +384,7 @@ POST /setup/db/provision { runtime: "docker"|"systemd"|"native" }
384384
- [x] Setup 모드일 때 메인 앱 라우트 전체 차단 (`createSetupApp()`, `index.ts` 분기)
385385
- [x] 설치 완료 시 `installed.lock` + `fieldstack.config.json` 생성 후 서버 자동 재시작
386386
- [x] `fieldstack.config.json``process.env` 자동 반영 (`applyConfigToEnv()`)
387-
- [ ] 완전 초기화 시 DB + `installed.lock` + config 삭제 → 서버 재시작 → Setup 모드 복귀
387+
- [x] 완전 초기화 시 DB + `installed.lock` + config 삭제 → 서버 재시작 → Setup 모드 복귀
388388

389389
#### 1.95.2 Setup 백엔드 API
390390
**예상 기간: 3일**
@@ -412,7 +412,7 @@ POST /setup/db/provision { runtime: "docker"|"systemd"|"native" }
412412
- [ ] 선택 옵션 (SMTP, 텔레메트리 동의 등) — Phase 2.3 / 3.5 이후 확장
413413
- [x] Progress 화면 (실시간 설치 로그, 단계 표시)
414414
- [x] Complete 화면 (로그인 진입 안내)
415-
- [ ] 설치 중 새로고침/재접속 복구 (진행 상태 재동기화)
415+
- [x] 설치 중 새로고침/재접속 복구 (진행 상태 재동기화)
416416
- [x] 각 단계 유효성 검증 UX (필수값, 형식 오류, DB 연결 테스트 결과)
417417
- [x] Progress 실패 처리 UX (재시도 / 이전 단계 복귀 / 에러 요약)
418418
- [x] Vite 개발 서버 API 프록시 설정 (`/setup`, `/core`, `/auth`, `/api``localhost:3000`)
@@ -478,7 +478,7 @@ Chrome 확장 프로그램의 "새로고침" 방식과 동일하게:
478478
#### P2-Pre.3 Admin UI 연동
479479
- [x] AdminView 모듈 관리 패널에 설치된 모듈 목록 표시 (`GET /core/modules` 연동)
480480
- [x] "모듈 새로고침" 버튼 추가 (`POST /core/modules/reload` 호출)
481-
- [ ] HomeView 관리자 통계 "설치 모듈" 카드를 실제 API 데이터로 교체
481+
- [x] HomeView 관리자 통계 "설치 모듈" 카드를 실제 API 데이터로 교체
482482

483483
#### P2-Pre.4 유저별 모듈 활성화 시스템
484484

@@ -503,7 +503,7 @@ Chrome 확장 프로그램의 "새로고침" 방식과 동일하게:
503503

504504
**Frontend:**
505505
- [x] 사이드바 모듈 메뉴를 `GET /core/modules/me` 기반으로 동적 구성 (AppShell 업데이트)
506-
- [ ] 설정 화면에서 유저별 모듈 활성화/비활성화 토글 UI
506+
- [x] 설정 화면에서 유저별 모듈 활성화/비활성화 토글 UI
507507

508508
---
509509

@@ -685,6 +685,8 @@ Chrome 확장 프로그램의 "새로고침" 방식과 동일하게:
685685
**예상 기간: 1주**
686686

687687
- [ ] 설치 마법사에 텔레메트리 동의 항목 추가 (opt-out 선택 가능)
688+
> ⚠️ **이 항목 작업 시 체크**: Setup 진행 중 새로고침/재접속 복구 기능(sessionStorage 복원 + `/setup/status` 폴링)을
689+
> 실제 설치 플로우에서 테스트할 것. 복구 배너 표시, 자동 complete 이동, 실패 후 재시도 시나리오 모두 확인.
688690
- [ ] 익명 설치 UUID 생성 및 관리
689691
- [ ] 오류/크래시 로그 수집 (에러 메시지, 스택 트레이스, 발생 컨텍스트)
690692
- [ ] 기능 사용 빈도 수집 (어떤 모듈·기능이 많이 쓰이는지)

0 commit comments

Comments
 (0)