@@ -10,6 +10,8 @@ import { HomeView } from "./views/HomeView";
1010import { LoginView } from "./views/LoginView" ;
1111import { SettingsView } from "./views/SettingsView" ;
1212import { AdminView } from "./views/AdminView" ;
13+ import { MarketplaceView } from "./views/MarketplaceView" ;
14+ import { ChangePasswordView } from "./views/ChangePasswordView" ;
1315
1416// ─── Types ────────────────────────────────────────────────────
1517type InstallMode = "normal" | "bypass" ;
@@ -37,10 +39,8 @@ function resolveInstallMode(runtimeEnv: WebRuntimeEnv): InstallMode {
3739
3840function getRouteFromHash ( rawHash : string ) : RouteKey {
3941 const hash = rawHash . replace ( "#" , "" ) ;
40- if ( hash === "settings" ) {
41- return "home" ;
42- }
43- if ( hash === "home" || hash === "admin" || hash === "login" ) {
42+ if ( hash === "settings" ) return "home" ;
43+ if ( hash === "home" || hash === "marketplace" || hash === "admin" || hash === "login" || hash === "change-password" ) {
4444 return hash ;
4545 }
4646 return "login" ;
@@ -55,9 +55,11 @@ function canAccessRoute(route: RouteKey, isAuthenticated: boolean): boolean {
5555
5656// ─── Session Storage Keys ─────────────────────────────────────
5757const SS = {
58- auth : "fs_auth" ,
59- admin : "fs_admin" ,
60- email : "fs_email" ,
58+ auth : "fs_auth" ,
59+ admin : "fs_admin" ,
60+ pinVerified : "fs_pin_verified" ,
61+ email : "fs_email" ,
62+ mustChangePw : "fs_must_change_pw" ,
6163} as const ;
6264
6365// ─── App Root ─────────────────────────────────────────────────
@@ -68,12 +70,18 @@ function App({ installMode }: { installMode: InstallMode }) {
6870 const [ isAdmin , setIsAdmin ] = useState (
6971 ( ) => sessionStorage . getItem ( SS . admin ) === "true" ,
7072 ) ;
73+ const [ isPinVerified , setIsPinVerified ] = useState (
74+ ( ) => sessionStorage . getItem ( SS . pinVerified ) === "true" ,
75+ ) ;
7176 const [ currentUser , setCurrentUser ] = useState < { email : string } | null > (
7277 ( ) => {
7378 const email = sessionStorage . getItem ( SS . email ) ;
7479 return email ? { email } : null ;
7580 } ,
7681 ) ;
82+ const [ mustChangePassword , setMustChangePassword ] = useState (
83+ ( ) => sessionStorage . getItem ( SS . mustChangePw ) === "true" ,
84+ ) ;
7785 const [ isSettingsOpen , setIsSettingsOpen ] = useState ( false ) ;
7886 const [ isPinModalOpen , setIsPinModalOpen ] = useState ( false ) ;
7987 const [ notice , setNotice ] = useState (
@@ -90,9 +98,11 @@ function App({ installMode }: { installMode: InstallMode }) {
9098 } , [ ] ) ;
9199
92100 const effectiveRoute = useMemo < RouteKey > ( ( ) => {
93- if ( canAccessRoute ( route , isAuthenticated ) ) return route ;
94- return "login" ;
95- } , [ isAuthenticated , route ] ) ;
101+ if ( ! canAccessRoute ( route , isAuthenticated ) ) return "login" ;
102+ // 비밀번호 변경 강제: change-password 이외의 모든 경로 차단
103+ if ( mustChangePassword && route !== "change-password" ) return "change-password" ;
104+ return route ;
105+ } , [ isAuthenticated , mustChangePassword , route ] ) ;
96106
97107 useEffect ( ( ) => {
98108 if ( window . location . hash !== `#${ effectiveRoute } ` ) {
@@ -108,14 +118,25 @@ function App({ installMode }: { installMode: InstallMode }) {
108118 // Auth handlers
109119 const onLogin = ( event : FormEvent < HTMLFormElement > ) => {
110120 event . preventDefault ( ) ;
111- const email = ( new FormData ( event . currentTarget ) . get ( "email" ) as string | null )
112- ?? "user@fieldstack.dev" ;
121+ const formData = new FormData ( event . currentTarget ) ;
122+ const email = ( formData . get ( "email" ) as string | null ) ?? "user@fieldstack.dev" ;
123+ // mock: 비밀번호가 "temp1234"이면 임시 비번 첫 로그인으로 처리
124+ const password = formData . get ( "password" ) as string | null ;
125+ const isTempLogin = password === "temp1234" ;
126+
113127 setIsAuthenticated ( true ) ;
114128 setCurrentUser ( { email } ) ;
115129 sessionStorage . setItem ( SS . auth , "true" ) ;
116130 sessionStorage . setItem ( SS . email , email ) ;
117- setNotice ( "Login successful (mock)." ) ;
118- navigate ( "home" ) ;
131+
132+ if ( isTempLogin ) {
133+ setMustChangePassword ( true ) ;
134+ sessionStorage . setItem ( SS . mustChangePw , "true" ) ;
135+ navigate ( "change-password" ) ;
136+ } else {
137+ setNotice ( "Login successful (mock)." ) ;
138+ navigate ( "home" ) ;
139+ }
119140 } ;
120141
121142 const onQuickLogin = ( ) => {
@@ -128,28 +149,31 @@ function App({ installMode }: { installMode: InstallMode }) {
128149 navigate ( "home" ) ;
129150 } ;
130151
131- const onAdminAccess = ( ) => {
132- if ( isAdmin ) {
133- navigate ( "admin" ) ;
134- } else {
135- setIsPinModalOpen ( true ) ;
136- }
152+ const onPasswordChanged = ( ) => {
153+ setMustChangePassword ( false ) ;
154+ sessionStorage . removeItem ( SS . mustChangePw ) ;
155+ setNotice ( "비밀번호가 변경되었습니다." ) ;
156+ navigate ( "home" ) ;
137157 } ;
138158
139159 const onPinVerified = ( ) => {
140160 setIsAdmin ( true ) ;
161+ setIsPinVerified ( true ) ;
141162 setIsPinModalOpen ( false ) ;
142163 sessionStorage . setItem ( SS . admin , "true" ) ;
164+ sessionStorage . setItem ( SS . pinVerified , "true" ) ;
143165 setNotice ( "관리자 인증 완료 (mock). 30분간 유효합니다." ) ;
144166 navigate ( "admin" ) ;
145167 } ;
146168
147169 const onLogout = ( ) => {
148170 setIsAuthenticated ( false ) ;
149171 setIsAdmin ( false ) ;
172+ setIsPinVerified ( false ) ;
150173 setCurrentUser ( null ) ;
151174 sessionStorage . removeItem ( SS . auth ) ;
152175 sessionStorage . removeItem ( SS . admin ) ;
176+ sessionStorage . removeItem ( SS . pinVerified ) ;
153177 sessionStorage . removeItem ( SS . email ) ;
154178 setNotice ( "Logged out." ) ;
155179 navigate ( "login" ) ;
@@ -170,6 +194,16 @@ function App({ installMode }: { installMode: InstallMode }) {
170194 ) ;
171195 }
172196
197+ // 비밀번호 강제 변경 (shell 없이 전체 화면)
198+ if ( effectiveRoute === "change-password" ) {
199+ return (
200+ < ChangePasswordView
201+ isFirstLogin = { mustChangePassword }
202+ onChanged = { onPasswordChanged }
203+ />
204+ ) ;
205+ }
206+
173207 return (
174208 < >
175209 { isPinModalOpen && (
@@ -185,13 +219,13 @@ function App({ installMode }: { installMode: InstallMode }) {
185219 currentUser = { currentUser }
186220 notice = { notice }
187221 onNavigate = { navigate }
188- onAdminAccess = { onAdminAccess }
189222 onLogout = { onLogout }
190223 onOpenSettings = { ( ) => setIsSettingsOpen ( true ) }
191224 >
192225 { effectiveRoute === "home" && < HomeView onOpenSettings = { ( ) => setIsSettingsOpen ( true ) } /> }
226+ { effectiveRoute === "marketplace" && < MarketplaceView /> }
193227 { effectiveRoute === "admin" && (
194- < AdminView isAdmin = { isAdmin } onRequestPin = { ( ) => setIsPinModalOpen ( true ) } />
228+ < AdminView isPinVerified = { isPinVerified } onRequestPin = { ( ) => setIsPinModalOpen ( true ) } />
195229 ) }
196230 { isSettingsOpen && (
197231 < SettingsView
@@ -200,6 +234,11 @@ function App({ installMode }: { installMode: InstallMode }) {
200234 onToggleAdmin = { ( ) => {
201235 setIsAdmin ( ( prev ) => {
202236 const next = ! prev ;
237+ if ( ! next ) {
238+ // 관리자 역할 해제 시 PIN 인증도 초기화
239+ setIsPinVerified ( false ) ;
240+ sessionStorage . removeItem ( SS . pinVerified ) ;
241+ }
203242 setNotice ( next ? "Admin authority enabled (mock)." : "Admin authority disabled." ) ;
204243 return next ;
205244 } ) ;
0 commit comments