Skip to content

Commit d78a54d

Browse files
SOIVclaude
andcommitted
feat(web): Phase 1.95.3 Setup Wizard UI 구현
- SetupWizardView: 4단계 설치 마법사 (Welcome → Config → Progress → Complete) - Welcome: 기능 소개 및 시작 버튼 - Config: 관리자 계정(이메일/비밀번호/PIN) + DB 선택(PostgreSQL 기본값/SQLite) - PostgreSQL: Docker/systemd/native 런타임 자동 감지 후 선택 - 자동 설치 버튼 (POST /setup/db/provision SSE 스트리밍) - 수동 URL 입력 + 연결 테스트 토글 - SQLite: "일부 모듈 기능이 정상 작동하지 않을 수 있음" 경고 - Progress: POST /setup/complete SSE 스트리밍 → 단계별 상태 표시 - Complete: 5초 카운트다운 후 로그인 페이지로 자동 이동 - main.tsx: AppRoot 래퍼 추가 — GET /setup/status 확인 후 설치 필요 시 마법사 표시 - setup-wizard.css: 전용 스타일 (다크/라이트 CSS 토큰 기반) - vite.config.ts: /setup, /core, /auth, /api, /health → localhost:3000 프록시 설정 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1276db8 commit d78a54d

4 files changed

Lines changed: 1424 additions & 2 deletions

File tree

apps/web/src/main.tsx

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
33

44
import "./styles/global.css";
55
import "./styles/login.css";
6+
import "./styles/setup-wizard.css";
67
import "@fieldstack/controls/styles";
78

89
import { AppShell, type RouteKey } from "./components/AppShell";
@@ -14,6 +15,7 @@ import { AdminView } from "./views/AdminView";
1415
import { MarketplaceView } from "./views/MarketplaceView";
1516
import { ChangePasswordView } from "./views/ChangePasswordView";
1617
import { ForgotPasswordView } from "./views/ForgotPasswordView";
18+
import { SetupWizardView } from "./views/SetupWizardView";
1719

1820
// ─── Types ────────────────────────────────────────────────────
1921
type InstallMode = "normal" | "bypass";
@@ -481,6 +483,45 @@ function App({ installMode }: { installMode: InstallMode }) {
481483
);
482484
}
483485

486+
// ─── Setup Status Check ───────────────────────────────────────
487+
type SetupStatus = "checking" | "required" | "done";
488+
489+
function AppRoot({ installMode }: { installMode: InstallMode }) {
490+
const [setupStatus, setSetupStatus] = useState<SetupStatus>("checking");
491+
492+
useEffect(() => {
493+
if (installMode === "bypass") {
494+
setSetupStatus("done");
495+
return;
496+
}
497+
fetch("/setup/status")
498+
.then((res) => res.json() as Promise<{ success: boolean; data?: { installed?: boolean } }>)
499+
.then((json) => {
500+
setSetupStatus(json.data?.installed === false ? "required" : "done");
501+
})
502+
.catch(() => {
503+
// API 연결 실패 시 정상 앱 모드로 진입 (설치 완료 후 서버 재시작 직후 등)
504+
setSetupStatus("done");
505+
});
506+
}, [installMode]);
507+
508+
if (setupStatus === "checking") {
509+
return (
510+
<div style={{ minHeight: "100vh", display: "flex", alignItems: "center", justifyContent: "center" }}>
511+
<span className="setup-spinner" style={{ width: 24, height: 24, borderWidth: 3 }} />
512+
</div>
513+
);
514+
}
515+
516+
if (setupStatus === "required") {
517+
return (
518+
<SetupWizardView onComplete={() => setSetupStatus("done")} />
519+
);
520+
}
521+
522+
return <App installMode={installMode} />;
523+
}
524+
484525
// ─── Bootstrap ────────────────────────────────────────────────
485526
const runtimeEnv = (import.meta as ImportMeta & { env?: WebRuntimeEnv }).env ?? {};
486527
const installMode = resolveInstallMode(runtimeEnv);
@@ -496,4 +537,4 @@ if (appRootElement === null) {
496537
throw new Error("App root element '#app' was not found.");
497538
}
498539

499-
createRoot(appRootElement).render(<App installMode={installMode} />);
540+
createRoot(appRootElement).render(<AppRoot installMode={installMode} />);

0 commit comments

Comments
 (0)