@@ -4,8 +4,6 @@ import { Button, FormField, PinInput } from "@fieldstack/controls";
44
55import "../styles/admin.css" ;
66
7- // 개발 mock PIN — 실제 구현 시 API 검증으로 교체
8- const MOCK_ADMIN_PIN = "1234" ;
97
108// 초기화 플로우 단계
119type ResetPhase =
@@ -94,6 +92,8 @@ export function AdminView({ isPinVerified, onRequestPin, installMode }: AdminVie
9492 const [ resetPhase , setResetPhase ] = useState < ResetPhase > ( "idle" ) ;
9593 const [ resetPin , setResetPin ] = useState ( "" ) ;
9694 const [ resetPinError , setResetPinError ] = useState ( "" ) ;
95+ const [ isResetting , setIsResetting ] = useState ( false ) ;
96+ const [ isPinChanging , setIsPinChanging ] = useState ( false ) ;
9797
9898 // 모듈 목록 조회
9999 const fetchModules = useCallback ( async ( ) => {
@@ -176,54 +176,84 @@ export function AdminView({ isPinVerified, onRequestPin, installMode }: AdminVie
176176 if ( next !== "system" ) resetResetFlow ( ) ;
177177 } ;
178178
179- // 부분 초기화 PIN 확인 (mock)
180- const handlePartialResetSubmit = ( e : FormEvent ) => {
179+ const handlePartialResetSubmit = async ( e : FormEvent ) => {
181180 e . preventDefault ( ) ;
182- if ( resetPin !== MOCK_ADMIN_PIN ) {
183- setResetPinError ( "PIN이 올바르지 않습니다." ) ;
184- setResetPin ( "" ) ;
185- return ;
186- }
181+ if ( isResetting ) return ;
182+ setIsResetting ( true ) ;
187183 setResetPinError ( "" ) ;
188- setResetPhase ( "p-done" ) ;
189- // 실제 구현 시: POST /admin/partial-reset { pin } 호출
184+ try {
185+ const token = sessionStorage . getItem ( "fs_token" ) ?? "" ;
186+ const res = await fetch ( "/admin/partial-reset" , {
187+ method : "POST" ,
188+ headers : { "Content-Type" : "application/json" , Authorization : `Bearer ${ token } ` } ,
189+ body : JSON . stringify ( { pin : resetPin } ) ,
190+ } ) ;
191+ const json = await res . json ( ) as { success : boolean ; error ?: string } ;
192+ if ( ! res . ok || ! json . success ) {
193+ setResetPinError ( json . error ?? "초기화 실패" ) ;
194+ setResetPin ( "" ) ;
195+ return ;
196+ }
197+ setResetPhase ( "p-done" ) ;
198+ } catch {
199+ setResetPinError ( "서버 연결 오류" ) ;
200+ } finally {
201+ setIsResetting ( false ) ;
202+ }
190203 } ;
191204
192- // 완전 초기화 PIN 확인 (mock)
193- const handleFactoryResetSubmit = ( e : FormEvent ) => {
205+ const handleFactoryResetSubmit = async ( e : FormEvent ) => {
194206 e . preventDefault ( ) ;
195- if ( resetPin !== MOCK_ADMIN_PIN ) {
196- setResetPinError ( "PIN이 올바르지 않습니다." ) ;
197- setResetPin ( "" ) ;
198- return ;
199- }
207+ if ( isResetting ) return ;
208+ setIsResetting ( true ) ;
200209 setResetPinError ( "" ) ;
201- setResetPhase ( "f-done" ) ;
202- // 실제 구현 시: POST /admin/factory-reset { pin } 호출 → 서버 재시작
210+ try {
211+ const token = sessionStorage . getItem ( "fs_token" ) ?? "" ;
212+ const res = await fetch ( "/admin/factory-reset" , {
213+ method : "POST" ,
214+ headers : { "Content-Type" : "application/json" , Authorization : `Bearer ${ token } ` } ,
215+ body : JSON . stringify ( { pin : resetPin } ) ,
216+ } ) ;
217+ const json = await res . json ( ) as { success : boolean ; error ?: string } ;
218+ if ( ! res . ok || ! json . success ) {
219+ setResetPinError ( json . error ?? "초기화 실패" ) ;
220+ setResetPin ( "" ) ;
221+ return ;
222+ }
223+ setResetPhase ( "f-done" ) ;
224+ } catch {
225+ setResetPinError ( "서버 연결 오류" ) ;
226+ } finally {
227+ setIsResetting ( false ) ;
228+ }
203229 } ;
204230
205- const handlePinChange = ( e : FormEvent ) => {
231+ const handlePinChange = async ( e : FormEvent ) => {
206232 e . preventDefault ( ) ;
207- if ( currentPin !== MOCK_ADMIN_PIN ) {
208- setPinError ( "현재 PIN이 올바르지 않습니다." ) ;
209- setCurrentPin ( "" ) ;
210- return ;
211- }
212- if ( newPin . length < 4 ) {
213- setPinError ( "새 PIN은 4자리 이상이어야 합니다." ) ;
214- return ;
215- }
216- if ( newPin !== confirmPin ) {
217- setPinError ( "새 PIN이 일치하지 않습니다." ) ;
218- setConfirmPin ( "" ) ;
219- return ;
220- }
221- if ( newPin === MOCK_ADMIN_PIN ) {
222- setPinError ( "현재 PIN과 동일한 PIN은 사용할 수 없습니다." ) ;
223- return ;
224- }
233+ if ( isPinChanging ) return ;
234+ if ( newPin . length < 4 ) { setPinError ( "새 PIN은 4자리 이상이어야 합니다." ) ; return ; }
235+ if ( newPin !== confirmPin ) { setPinError ( "새 PIN이 일치하지 않습니다." ) ; setConfirmPin ( "" ) ; return ; }
236+ setIsPinChanging ( true ) ;
225237 setPinError ( "" ) ;
226- setPinSuccess ( true ) ;
238+ try {
239+ const token = sessionStorage . getItem ( "fs_token" ) ?? "" ;
240+ const res = await fetch ( "/admin/change-pin" , {
241+ method : "POST" ,
242+ headers : { "Content-Type" : "application/json" , Authorization : `Bearer ${ token } ` } ,
243+ body : JSON . stringify ( { currentPin, newPin } ) ,
244+ } ) ;
245+ const json = await res . json ( ) as { success : boolean ; error ?: string } ;
246+ if ( ! res . ok || ! json . success ) {
247+ setPinError ( json . error ?? "PIN 변경 실패" ) ;
248+ setCurrentPin ( "" ) ;
249+ return ;
250+ }
251+ setPinSuccess ( true ) ;
252+ } catch {
253+ setPinError ( "서버 연결 오류" ) ;
254+ } finally {
255+ setIsPinChanging ( false ) ;
256+ }
227257 } ;
228258
229259 if ( ! isPinVerified ) {
0 commit comments