@@ -17,7 +17,8 @@ export default function ManitoVerifyPage() {
1717 const [ loading , setLoading ] = useState ( false ) ;
1818 const [ error , setError ] = useState ( '' ) ;
1919 const [ cipher , setCipher ] = useState ( '' ) ;
20- const [ plain , setPlain ] = useState ( null ) ; // 복호화 결과(JSON 등)
20+ const [ plain , setPlain ] = useState ( null ) ; // { receiverStudentId, receiverName }
21+ const [ ownerName , setOwnerName ] = useState ( '' ) ; // 요청자 이름 (API에서 내려주는 값 가정)
2122
2223 useEffect ( ( ) => {
2324 if ( ! searchParams ) return ;
@@ -36,6 +37,7 @@ export default function ManitoVerifyPage() {
3637 let base64 = str . replace ( / - / g, '+' ) . replace ( / _ / g, '/' ) ;
3738 while ( base64 . length % 4 !== 0 ) base64 += '=' ;
3839 const binary = typeof atob !== 'undefined' ? atob ( base64 ) : Buffer . from ( base64 , 'base64' ) . toString ( 'binary' ) ;
40+
3941 const bytes = new Uint8Array ( binary . length ) ;
4042 for ( let i = 0 ; i < binary . length ; i ++ ) {
4143 bytes [ i ] = binary . charCodeAt ( i ) ;
@@ -93,6 +95,7 @@ export default function ManitoVerifyPage() {
9395 if ( decoded ) {
9496 setPlain ( decoded ) ;
9597 } else {
98+ // 여기서도 error 세팅
9699 setError ( ( prev ) => ( prev ? prev + '\n' : '' ) + '복호화에 실패했습니다. 해시 값이 올바른지 확인해 주세요.' , ) ;
97100 setPlain ( null ) ;
98101 }
@@ -108,6 +111,7 @@ export default function ManitoVerifyPage() {
108111 setError ( '' ) ;
109112 setCipher ( '' ) ;
110113 setPlain ( null ) ;
114+ setOwnerName ( '' ) ;
111115
112116 if ( ! sessionCode || ! studentId ) {
113117 setError ( '세션 코드 또는 학번 정보가 잘못되었습니다. 링크를 다시 확인해 주세요.' ) ;
@@ -126,10 +130,12 @@ export default function ManitoVerifyPage() {
126130
127131 const body = res . data ;
128132
129- // ✅ 서버 응답 키 이름에 맞게 수정 (encryptedManito)
133+ // ✅ 서버 응답 키 이름 맞춰서 사용
130134 const encrypted = body ?. data ?. encryptedManito || '' ;
135+ const owner = body ?. data ?. ownerName || '' ; // 백엔드에서 내려준다고 가정
131136
132- setCipher ( encrypted ) ; // 🔥 cipher가 바뀌면 위 useEffect가 hash로 복호화
137+ setOwnerName ( owner ) ;
138+ setCipher ( encrypted ) ; // 🔥 cipher가 바뀌면 useEffect가 hash로 복호화
133139 } catch ( err ) {
134140 const res = err ?. response ;
135141 const msg = res ?. data ?. message || res ?. data ?. error || err ?. message || '알 수 없는 오류가 발생했습니다.' ;
@@ -141,66 +147,101 @@ export default function ManitoVerifyPage() {
141147
142148 const disabled = ! sessionCode || ! studentId ;
143149
144- return ( < div className = "dark min-h-[100svh] flex items-center justify-center bg-black px-4" >
145- < Card className = "max-w-md w-full bg-zinc-900 border border-zinc-800" >
146- < CardHeader className = "flex flex-col items-start gap-2" >
147- < h1 className = "text-2xl font-bold text-white" > 마니또 확인</ h1 >
148- < p className = "text-sm text-zinc-400" >
149- 전달받은 링크로 접속한 뒤, 본인이 설정한 PIN 4자리를 입력해 주세요.
150- </ p >
151- </ CardHeader >
152- < Divider className = "border-zinc-800" />
153- < CardBody className = "flex flex-col gap-4 text-white" >
154- { disabled && ( < p className = "text-xs text-red-400" >
155- 필수 정보가 누락되었습니다. 링크를 다시 확인해 주세요.
156- </ p > ) }
157-
158- < form onSubmit = { handleSubmit } className = "flex flex-col gap-4 mt-2" >
159- < Input
160- label = "PIN (숫자 4자리)"
161- type = "password"
162- variant = "bordered"
163- value = { pin }
164- maxLength = { 4 }
165- onChange = { ( e ) => setPin ( e . target . value . replace ( / [ ^ 0 - 9 ] / g, '' ) . slice ( 0 , 4 ) ) }
166- classNames = { {
167- label : 'text-zinc-300' ,
168- input : 'text-white' ,
169- inputWrapper : 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-400' ,
170- } }
171- isDisabled = { disabled || loading }
172- />
173-
174- { error && ( < p className = "text-xs text-red-400 whitespace-pre-line" >
175- { error }
176- </ p > ) }
177-
178- < Button
179- type = "submit"
180- color = "primary"
181- isLoading = { loading }
182- isDisabled = { disabled || loading || ! pin }
183- className = "font-semibold"
184- >
150+ // 결과 문구 구성
151+ const receiverName = plain ?. receiverName ;
152+ const ownerLabel = ownerName || '당신' ;
153+
154+ return ( < div
155+ className = "dark min-h-[100svh] flex items-center justify-center bg-gradient-to-br from-zinc-950 via-black to-zinc-900 px-4"
156+ >
157+ < Card className = "max-w-md w-full bg-zinc-900/90 border border-zinc-800 shadow-2xl" >
158+ < CardHeader className = "flex flex-col gap-2 items-start" >
159+ < span className = "text-xs text-zinc-400" > 🎁 GDGoC INHA · 마니또</ span >
160+ < h1 className = "text-2xl font-bold text-white" >
185161 마니또 확인하기
186- </ Button >
187- </ form >
188-
189- { /* 결과 영역 */ }
190- { ( cipher || plain ) && ( < >
191- < Divider className = "border-zinc-800 my-2" />
192- < div className = "space-y-2 text-sm" >
193- < p className = "font-semibold text-zinc-200" > 결과</ p >
194-
195- { plain ? ( < >
196- < pre className = "text-xs bg-zinc-950 border border-zinc-800 rounded-lg p-3 overflow-x-auto" >
197- { JSON . stringify ( plain , null , 2 ) }
198- </ pre >
199- </ > ) : ( < >
162+ </ h1 >
163+ < p className = "text-xs text-zinc-400" >
164+ 전달받은 링크로 접속한 뒤,< br />
165+ 본인이 설정한 PIN 4자리를 입력해 주세요.
166+ </ p >
167+ </ CardHeader >
168+
169+ < Divider className = "border-zinc-800" />
170+
171+ < CardBody className = "flex flex-col gap-4 text-white" >
172+ { disabled && ( < p className = "text-xs text-red-400" >
173+ 필수 정보가 누락되었습니다. 링크를 다시 확인해 주세요.
174+ </ p > ) }
175+
176+ < form onSubmit = { handleSubmit } className = "flex flex-col gap-4 mt-1" >
177+ < Input
178+ label = "PIN (숫자 4자리)"
179+ type = "password"
180+ variant = "bordered"
181+ value = { pin }
182+ maxLength = { 4 }
183+ placeholder = "ex) 0420"
184+ onChange = { ( e ) => setPin ( e . target . value . replace ( / [ ^ 0 - 9 ] / g, '' ) . slice ( 0 , 4 ) , ) }
185+ classNames = { {
186+ label : 'text-zinc-300' ,
187+ input : 'text-white text-base tracking-[0.25em]' ,
188+ inputWrapper : 'bg-zinc-900 border-zinc-700 group-data-[focus=true]:border-zinc-300' ,
189+ } }
190+ isDisabled = { disabled || loading }
191+ />
192+
193+ { error && ( < p className = "text-xs text-red-400 whitespace-pre-line" >
194+ { error }
195+ </ p > ) }
196+
197+ < Button
198+ type = "submit"
199+ color = "primary"
200+ isLoading = { loading }
201+ isDisabled = { disabled || loading || ! pin }
202+ className = "font-semibold bg-gradient-to-r from-sky-500 to-cyan-400 text-black"
203+ >
204+ 마니또 확인하기 ✨
205+ </ Button >
206+ </ form >
207+
208+ { /* 결과 영역 */ }
209+ { ( cipher || plain ) && ( < >
210+ < Divider className = "border-zinc-800 my-2" />
211+ < div className = "space-y-3 text-sm" >
212+ < p className = "text-xs text-zinc-400" >
213+ 결과
214+ </ p >
215+
216+ { plain ? ( < div
217+ className = "rounded-lg bg-zinc-950/80 border border-zinc-800 px-4 py-3 space-y-2"
218+ >
219+ < p className = "text-base font-semibold" >
220+ { ownerLabel }
221+ < span className = "text-zinc-300" > 의 마니또는 </ span >
222+ < span className = "text-sky-400" >
223+ { receiverName || '알 수 없음' }
224+ </ span >
225+ < span className = "text-zinc-300" > 님입니다! 🎉</ span >
226+ </ p >
227+ < p className = "text-[11px] text-zinc-500 mt-2" >
228+ 이 정보는 브라우저에서 해시값을 사용해 복호화한 결과이며,
229+ 서버에는 평문으로 저장되지 않습니다.
230+ </ p >
231+ </ div > ) : ( < div
232+ className = "rounded-lg bg-zinc-950/80 border border-red-500/40 px-4 py-3 space-y-1"
233+ >
234+ < p className = "text-sm font-semibold text-red-400" >
235+ 복호화에 실패하였습니다.
236+ </ p >
237+ < p className = "text-[11px] text-zinc-500" >
238+ 해시 값이 올바른지, 전달받은 링크가 정확한지 다시 한 번
239+ 확인해 주세요. 문제가 계속되면 운영진에게 문의해 주세요.
240+ </ p >
241+ </ div > ) }
242+ </ div >
200243 </ > ) }
201- </ div >
202- </ > ) }
203- </ CardBody >
204- </ Card >
205- </ div > ) ;
244+ </ CardBody >
245+ </ Card >
246+ </ div > ) ;
206247}
0 commit comments