33import { useEffect , useState } from 'react' ;
44import { useSearchParams } from 'next/navigation' ;
55import { Button , Card , CardBody , CardHeader , Divider , Input } from '@nextui-org/react' ;
6+ import { useAuthenticatedApi } from '@/hooks/useAuthenticatedApi' ;
67
78export default function ManitoVerifyPage ( ) {
89 const searchParams = useSearchParams ( ) ;
10+ const { apiClient} = useAuthenticatedApi ( ) ;
911
1012 const [ sessionCode , setSessionCode ] = useState ( '' ) ;
1113 const [ studentId , setStudentId ] = useState ( '' ) ;
@@ -58,7 +60,6 @@ export default function ManitoVerifyPage() {
5860
5961 const keyBytes = base64UrlToBytes ( hashKey ) ;
6062 if ( keyBytes . length !== 32 ) {
61- // 우리가 정의한 스펙: 32바이트 키
6263 console . warn ( 'Unexpected key length:' , keyBytes . length ) ;
6364 return null ;
6465 }
@@ -95,21 +96,9 @@ export default function ManitoVerifyPage() {
9596 try {
9697 setLoading ( true ) ;
9798
98- const res = await fetch ( '/api/v1/manito/verify' , {
99- method : 'POST' , headers : {
100- 'Content-Type' : 'application/json' ,
101- } , body : JSON . stringify ( {
102- sessionCode, studentId, pin,
103- } ) ,
104- } ) ;
105-
106- if ( ! res . ok ) {
107- const body = await res . json ( ) . catch ( ( ) => null ) ;
108- const msg = body ?. message || body ?. error || `요청이 실패했습니다. (status ${ res . status } )` ;
109- throw new Error ( msg ) ;
110- }
99+ const res = await apiClient . post ( '/manito/verify' , { sessionCode, studentId, pin} , { } , ) ;
111100
112- const body = await res . json ( ) ;
101+ const body = res . data ;
113102 const encrypted = body ?. data ?. encryptedManitto || '' ;
114103
115104 setCipher ( encrypted ) ;
@@ -120,12 +109,13 @@ export default function ManitoVerifyPage() {
120109 if ( decoded ) {
121110 setPlain ( decoded ) ;
122111 } else {
123- // 해시는 있는데 복호화 실패 → 에러만 살짝 알려주고 암호문은 그대로 보여줌
124112 setError ( ( prev ) => ( prev ? prev + '\n' : '' ) + '복호화에 실패했습니다. 해시 값이 올바른지 확인해 주세요.' , ) ;
125113 }
126114 }
127115 } catch ( err ) {
128- setError ( err . message || '알 수 없는 오류가 발생했습니다.' ) ;
116+ const res = err ?. response ;
117+ const msg = res ?. data ?. message || res ?. data ?. error || err ?. message || '알 수 없는 오류가 발생했습니다.' ;
118+ setError ( msg ) ;
129119 } finally {
130120 setLoading ( false ) ;
131121 }
@@ -134,90 +124,90 @@ export default function ManitoVerifyPage() {
134124 const disabled = ! sessionCode || ! studentId ;
135125
136126 return ( < div className = "dark min-h-[100svh] flex items-center justify-center bg-black px-4" >
137- < Card className = "max-w-md w-full bg-zinc-900 border border-zinc-800" >
138- < CardHeader className = "flex flex-col items-start gap-2" >
139- < h1 className = "text-2xl font-bold text-white" > 마니또 확인</ h1 >
140- < p className = "text-sm text-zinc-400" >
141- 전달받은 링크로 접속한 뒤, 본인이 설정한 PIN 4자리를 입력해 주세요.
142- </ p >
143- </ CardHeader >
144- < Divider className = "border-zinc-800" />
145- < CardBody className = "flex flex-col gap-4 text-white" >
146- { /* 세션/학번 정보 표시 (읽기 전용) */ }
147- < div className = "text-xs text-zinc-400 space-y-1" >
148- < div >
149- < span className = "font-semibold text-zinc-300" > 세션 코드: </ span >
150- < span > { sessionCode || '(없음)' } </ span >
151- </ div >
152- < div >
153- < span className = "font-semibold text-zinc-300" > 학번: </ span >
154- < span > { studentId || '(없음)' } </ span >
155- </ div >
156- { hash && ( < div >
157- < span className = "font-semibold text-zinc-300" > 해시: </ span >
158- < span className = "break-all" > { hash } </ span >
159- </ div > ) }
127+ < Card className = "max-w-md w-full bg-zinc-900 border border-zinc-800" >
128+ < CardHeader className = "flex flex-col items-start gap-2" >
129+ < h1 className = "text-2xl font-bold text-white" > 마니또 확인</ h1 >
130+ < p className = "text-sm text-zinc-400" >
131+ 전달받은 링크로 접속한 뒤, 본인이 설정한 PIN 4자리를 입력해 주세요.
132+ </ p >
133+ </ CardHeader >
134+ < Divider className = "border-zinc-800" />
135+ < CardBody className = "flex flex-col gap-4 text-white" >
136+ { /* 세션/학번 정보 표시 (읽기 전용) */ }
137+ < div className = "text-xs text-zinc-400 space-y-1" >
138+ < div >
139+ < span className = "font-semibold text-zinc-300" > 세션 코드: </ span >
140+ < span > { sessionCode || '(없음)' } </ span >
160141 </ div >
161-
162- { disabled && ( < p className = "text-xs text-red-400" >
163- 세션 코드 또는 학번 정보가 누락되었습니다. 링크를 다시 확인해 주세요.
164- </ p > ) }
165-
166- < form onSubmit = { handleSubmit } className = "flex flex-col gap-4 mt-2" >
167- < Input
168- label = "PIN (숫자 4자리)"
169- type = "password"
170- variant = "bordered"
171- value = { pin }
172- maxLength = { 4 }
173- onChange = { ( e ) => setPin ( e . target . value . replace ( / [ ^ 0 - 9 ] / g, '' ) . slice ( 0 , 4 ) ) }
174- classNames = { {
175- label : 'text-zinc-300' ,
176- input : 'text-white' ,
177- inputWrapper : 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-400' ,
178- } }
179- isDisabled = { disabled || loading }
180- />
181-
182- { error && ( < p className = "text-xs text-red-400 whitespace-pre-line" > { error } </ p > ) }
183-
184- < Button
185- type = "submit"
186- color = "primary"
187- isLoading = { loading }
188- isDisabled = { disabled || loading || ! pin }
189- className = "font-semibold"
190- >
191- 마니또 확인하기
192- </ Button >
193- </ form >
194-
195- { /* 결과 영역 */ }
196- { ( cipher || plain ) && ( < >
197- < Divider className = "border-zinc-800 my-2" />
198- < div className = "space-y-2 text-sm" >
199- < p className = "font-semibold text-zinc-200" > 결과</ p >
200-
201- { plain ? ( < >
142+ < div >
143+ < span className = "font-semibold text-zinc-300" > 학번: </ span >
144+ < span > { studentId || '(없음)' } </ span >
145+ </ div >
146+ { hash && ( < div >
147+ < span className = "font-semibold text-zinc-300" > 해시: </ span >
148+ < span className = "break-all" > { hash } </ span >
149+ </ div > ) }
150+ </ div >
151+
152+ { disabled && ( < p className = "text-xs text-red-400" >
153+ 세션 코드 또는 학번 정보가 누락되었습니다. 링크를 다시 확인해 주세요.
154+ </ p > ) }
155+
156+ < form onSubmit = { handleSubmit } className = "flex flex-col gap-4 mt-2" >
157+ < Input
158+ label = "PIN (숫자 4자리)"
159+ type = "password"
160+ variant = "bordered"
161+ value = { pin }
162+ maxLength = { 4 }
163+ onChange = { ( e ) => setPin ( e . target . value . replace ( / [ ^ 0 - 9 ] / g, '' ) . slice ( 0 , 4 ) ) }
164+ classNames = { {
165+ label : 'text-zinc-300' ,
166+ input : 'text-white' ,
167+ inputWrapper : 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-400' ,
168+ } }
169+ isDisabled = { disabled || loading }
170+ />
171+
172+ { error && ( < p className = "text-xs text-red-400 whitespace-pre-line" > { error } </ p > ) }
173+
174+ < Button
175+ type = "submit"
176+ color = "primary"
177+ isLoading = { loading }
178+ isDisabled = { disabled || loading || ! pin }
179+ className = "font-semibold"
180+ >
181+ 마니또 확인하기
182+ </ Button >
183+ </ form >
184+
185+ { /* 결과 영역 */ }
186+ { ( cipher || plain ) && ( < >
187+ < Divider className = "border-zinc-800 my-2" />
188+ < div className = "space-y-2 text-sm" >
189+ < p className = "font-semibold text-zinc-200" > 결과</ p >
190+
191+ { plain ? ( < >
202192 < pre
203193 className = "text-xs bg-zinc-950 border border-zinc-800 rounded-lg p-3 overflow-x-auto" >
204194 { JSON . stringify ( plain , null , 2 ) }
205195 </ pre >
206- < p className = "text-xs text-zinc-400" >
207- 위 내용은 클라이언트에서 hash를 이용해 복호화한 결과입니다.
208- </ p >
209- </ > ) : ( < >
210- < p className = "text-xs text-zinc-400 mb-1" >
211- 서버에서 받은 암호문(encryptedManitto)입니다.
212- </ p >
213- < pre
214- className = "text-xs bg-zinc-950 border border-zinc-800 rounded-lg p-3 break-all" >
196+ < p className = "text-xs text-zinc-400" >
197+ 위 내용은 클라이언트에서 hash를 이용해 복호화한 결과입니다.
198+ </ p >
199+ </ > ) : ( < >
200+ < p className = "text-xs text-zinc-400 mb-1" >
201+ 서버에서 받은 암호문(encryptedManitto)입니다.
202+ </ p >
203+ < pre
204+ className = "text-xs bg-zinc-950 border border-zinc-800 rounded-lg p-3 break-all" >
215205 { cipher }
216206 </ pre >
217- </ > ) }
218- </ div >
219207 </ > ) }
220- </ CardBody >
221- </ Card >
222- </ div > ) ;
208+ </ div >
209+ </ > ) }
210+ </ CardBody >
211+ </ Card >
212+ </ div > ) ;
223213}
0 commit comments