Skip to content

Commit 89282d6

Browse files
SOIVclaude
andcommitted
feat(web): OTP 인증 화면을 LoginView 인라인 step으로 통합
- #otp 라우트 제거, LoginView 내부 step('credentials'|'otp') 전환으로 대체 - 상단 레이블 'Sign in' → '2FA OTP' 전환 (기존 디자인 유지) - OtpView.tsx, otp.css 삭제, 로직은 LoginView로 이관 - RouteKey에서 'otp' 제거 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e09708b commit 89282d6

6 files changed

Lines changed: 208 additions & 297 deletions

File tree

apps/web/src/components/AppShell.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { ReactNode } from "react";
22
import "../styles/shell.css";
33

4-
export type RouteKey = "login" | "otp" | "forgot-password" | "home" | "marketplace" | "admin" | "change-password";
4+
export type RouteKey = "login" | "forgot-password" | "home" | "marketplace" | "admin" | "change-password";
55

66
interface AppShellProps {
77
installMode: "normal" | "bypass";

apps/web/src/main.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { SettingsView } from "./views/SettingsView";
1313
import { AdminView } from "./views/AdminView";
1414
import { MarketplaceView } from "./views/MarketplaceView";
1515
import { ChangePasswordView } from "./views/ChangePasswordView";
16-
import { OtpView } from "./views/OtpView";
1716
import { ForgotPasswordView } from "./views/ForgotPasswordView";
1817

1918
// ─── Types ────────────────────────────────────────────────────
@@ -43,7 +42,7 @@ function resolveInstallMode(runtimeEnv: WebRuntimeEnv): InstallMode {
4342
function getRouteFromHash(rawHash: string): RouteKey {
4443
const hash = rawHash.replace("#", "");
4544
if (hash === "settings") return "home";
46-
const valid: RouteKey[] = ["login", "otp", "forgot-password", "home", "marketplace", "admin", "change-password"];
45+
const valid: RouteKey[] = ["login", "forgot-password", "home", "marketplace", "admin", "change-password"];
4746
return (valid as string[]).includes(hash) ? (hash as RouteKey) : "login";
4847
}
4948

@@ -125,8 +124,8 @@ function App({ installMode }: { installMode: InstallMode }) {
125124
}, []);
126125

127126
const effectiveRoute = useMemo<RouteKey>(() => {
128-
// OTP 대기 중: otp 화면만 허용
129-
if (pendingOtpEmail) return "otp";
127+
// OTP 대기 중: login 화면 유지 (LoginView 내부에서 step 전환)
128+
if (pendingOtpEmail) return "login";
130129
// 미인증: login / forgot-password만 허용
131130
if (!isAuthenticated) {
132131
return route === "forgot-password" ? "forgot-password" : "login";
@@ -154,10 +153,9 @@ function App({ installMode }: { installMode: InstallMode }) {
154153
const email = (formData.get("email") as string | null) ?? "user@fieldstack.dev";
155154
const password = formData.get("password") as string | null;
156155

157-
// mock: "otp1234" → 2FA OTP 화면으로 이동
156+
// mock: "otp1234" → LoginView 내 OTP step으로 전환
158157
if (password === "otp1234") {
159158
setPendingOtpEmail(email);
160-
navigate("otp");
161159
return;
162160
}
163161

@@ -236,7 +234,7 @@ function App({ installMode }: { installMode: InstallMode }) {
236234
navigate("login");
237235
};
238236

239-
// Login page (no shell)
237+
// Login page (no shell) — OTP step도 이 안에서 처리
240238
if (effectiveRoute === "login") {
241239
return (
242240
<main className="auth-shell">
@@ -246,6 +244,9 @@ function App({ installMode }: { installMode: InstallMode }) {
246244
onQuickLogin={onQuickLogin}
247245
onForgotPassword={() => navigate("forgot-password")}
248246
showDevBypass={installMode === "bypass"}
247+
pendingEmail={pendingOtpEmail}
248+
onOtpVerified={onOtpVerified}
249+
onOtpCancel={onOtpCancel}
249250
/>
250251
</section>
251252
</main>
@@ -257,17 +258,6 @@ function App({ installMode }: { installMode: InstallMode }) {
257258
return <ForgotPasswordView onBack={() => navigate("login")} />;
258259
}
259260

260-
// 2FA OTP 인증 (no shell)
261-
if (effectiveRoute === "otp") {
262-
return (
263-
<OtpView
264-
email={pendingOtpEmail ?? ""}
265-
onVerified={onOtpVerified}
266-
onCancel={onOtpCancel}
267-
/>
268-
);
269-
}
270-
271261
// 비밀번호 강제 변경 (shell 없이 전체 화면)
272262
if (effectiveRoute === "change-password") {
273263
return (

apps/web/src/styles/login.css

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
transform: translateX(-50px);
101101
}
102102

103+
/* ── Top label ──────────────────────────────────────────────── */
103104
.login-top-label {
104105
position: absolute;
105106
top: 0;
@@ -122,10 +123,35 @@
122123
font-size: 14px;
123124
font-weight: 700;
124125
letter-spacing: 0.04em;
125-
text-transform: none;
126126
color: var(--accent-hover);
127127
}
128128

129+
/* ── OTP Step (login-panel 내부) ────────────────────────────── */
130+
.login-otp-head {
131+
display: grid;
132+
gap: 8px;
133+
justify-items: center;
134+
text-align: center;
135+
margin-bottom: 4px;
136+
}
137+
138+
.login-otp-icon {
139+
font-size: 32px;
140+
line-height: 1;
141+
}
142+
143+
.login-otp-form {
144+
width: 100%;
145+
max-width: 340px;
146+
gap: 16px;
147+
}
148+
149+
.login-otp-footer {
150+
display: grid;
151+
gap: 4px;
152+
justify-items: center;
153+
}
154+
129155
.login-dev-badge {
130156
border-radius: 999px;
131157
padding: 2px 8px;

apps/web/src/styles/otp.css

Lines changed: 0 additions & 118 deletions
This file was deleted.

0 commit comments

Comments
 (0)