개그의 정답을 맞추면 체크 아이콘과 함께 인정vs아재 버튼이 활성화된다.
사용 기술 - react-query, styled-components, react, type-script
깃허브 페이지 - https://github.com/Nidurolak/asd/blob/master/src/pages/GagDetail.tsx
이 페이지는 시간 제한에 따라 문제를 맞추는지 못 맞추는지를 결정하는 퀴즈 페이지이다. 여기서 문제를 맞추느냐 시간초과가 되느냐에 따라 정답률에 영향을 끼친다.(회원일 때만)
const [ gagData , setGagData ] = useState < GagDetailContent >()
const { isLoading } = useQuery ([ "getList" , { Id: pam . id }],
() => getGagDetailPage ({ Id: detailId }),
{
onSuccess : ({ data }) => {
setGagData ( data . data )
if ( getCookie ( "token" ) == null || undefined ){
const savedSolvedList = getLocalStorage ( "solvedList" );
if ( savedSolvedList !== null ) {
if ( savedSolvedList . indexOf ( detailId as string ) == - 1 ){
console . log ( savedSolvedList )
console . log ( detailId )
setLocalStorage ( "solvedList" , [ ... savedSolvedList . split ( ',' ), detailId ])
}
}
}
}
}
)
비 회원일 경우에만 (토큰 감지)현재 파라미터에서 접속한 개그의 id값을 가져와 로컬 스토리지에 저장시킨다. 이는 개그 리스트에서 비회원일 경우 활용된다. 그리고 기본적으로 gagData에 값을 넣는다.
gagData에 있는 타입 GagDetailContent는
export interface GagDetailContent {
title : string ;
content : string ;
gagId : string ;
nextGagId : number | null ;
prevGagId : number | null ;
}
이런 구조로 이루어진 커스텀 타입이다.
const moveOtherGag = ( value : number ) => {
if ( value != 0 ) {
navigate ( `/GagDetail/?id= ${ value } ` )
window . location . reload ()
}
else { window . alert ( "더 이상 개그가 없습니다!" ) }
}
이 부분은 다음 개그로 이동하는 부분이다. 별다른 기능은 없고 gagData에 이전 혹은 이후 개그의 ID가 없는, 끝 번호인 상황에서 윈도우 얼럿창을 띄우도록 한 것이다.
const GagupMutation = useMutation < any >( postGagAnswer , {
onSuccess : ( responseData ) => {
if ( responseData . data . data . answer == "timeout" ) {
isAnswerSubmitted . current = true ;
if ( responseData . data . data . agree == true ){ setSelectedOption ( "injung" ) }
if ( responseData . data . data . ajae == true ){ setSelectedOption ( "ajae" ) }
setAnswerEnd ( true )
window . alert ( `시간 초과! 정답은 " ${ responseData . data . data . realAnswer } "` )
}
else if ( responseData . data . data . answer == "오답입니다." && timeOut . current == false ) {
setAnswerEnd ( true );
isAnswerSubmitted . current = false ;
setTimeout (() => { setAnswerEnd ( false ); }, 1300 );
}
else if ( timeOut . current == false ) {
setAnimationPaused ( true )
isAnswerSubmitted . current = true ;
timeOut . current = true
setAnswerEnd ( true )
if ( responseData . data . data . agree == true ){ setSelectedOption ( "injung" ) }
if ( responseData . data . data . ajae == true ){ setSelectedOption ( "ajae" ) }
}
},
onError : ( error ) => {
console . log ( "업로드 실패" )
},
});
const postAnswer = async ( data : any ) => {
if ( isAnswerSubmitted . current == false ) {
const payload : any = {
id: detailId ,
answer: data . answer ,
};
if ( answerEnd !== true ) { const res = await GagupMutation . mutateAsync ( payload ); }
}
};
이 부분에서 상당히 많은 고생을 했다. 개그의 정답을 맞추는 post 계열 api와 연계되는데,
타임 오버가 아닌 상황
타임 오버가 된 상황
정답일 경우
오답일 경우
timeout을 정답으로 전송
타임 관련 로직 올 스탑
X 아이콘 출력으로 끝
받아온 정답을 노출시킴
회원일 경우
아재 / 인정에 체크한 부분 강조
위 표의 로직을 거친다고 보면 된다. 말로 하면 복잡해서 표로 작성했으니 표와 코드를 번갈아 보며 확인해도 된다.
const postInjungOrAjae = async ( isAjaebool : boolean ) => {
var payload : any = {
id: detailId ,
isAjae: isAjaebool
}
const res = await InjungAjaeMutation . mutateAsync ( payload )
}
이 코드는 인정(추천), 아재(비추천)을 날리는 리액트 쿼리인데, paylaod의 data 타입을 커스텀 타입으로 하려고 했지만 계속된 에러로 any 타입을 지정할 수 밖에 없었다. 이 부분은 트러블 슈팅에서 해결해볼 생각이다.
return ( < BackgroundBox >
< GagBackGround onSubmit = { handleSubmit ( postAnswer ) } >
< GagNameBox >
< h3 > { gagData ?. title } </ h3 >
</ GagNameBox >
< GagContentBox >
< h4 > { gagData ?. content } </ h4 >
</ GagContentBox >
< GageBackGround >
< GageFront paused = { animationPaused } />
</ GageBackGround >
< Controller
name = "answer"
control = { control }
defaultValue = ""
render = { () => (
< InputStyle
{ ... register ( "answer" , {
required: "정답을 입력하세요."
}) } /> ) }
/>
</ GagBackGround >
{ ( animationPaused && answerEnd && timeOut ) && < CheckMark src = { checkmark } /> }
{ ( answerEnd && ! animationPaused ) && < CheckMark src = { cancel } /> }
{ timeOut . current &&
< MoveOtherGagBox >
< RadioButton onClick = { () => moveOtherGag ( gagData ?. nextGagId as number ) } >< span > < </ span ></ RadioButton >
< InjungAjaeTextBox >
< InjungAjaeText >
< RadioButton onClick = { () => handleButtonClick ( 'injung' ) } isSelected = { selectedOption === 'injung' } > 인정! </ RadioButton >
< span > VS </ span >
< RadioButton onClick = { () => handleButtonClick ( 'ajae' ) } isSelected = { selectedOption === 'ajae' } > 아재! </ RadioButton >
</ InjungAjaeText >
< p > 선택하고 다음 개그로! </ p >
</ InjungAjaeTextBox >
< RadioButton onClick = { () => moveOtherGag ( gagData ?. prevGagId as number ) } >< span > > </ span ></ RadioButton >
</ MoveOtherGagBox > }
< p onClick = { () => { navigate ( '/Gaglist' )} } > 목록으로 </ p >
</ BackgroundBox > );
}
들어간 로직이 복잡미묘한 것에 비해 div 요소들은 생각보다 깔끔한 편이다. 리액트 훅 폼을 사용하는 Controller 부분이 좀 지저분하지만 저건 어쩔 수 없다. 괜히 건드렸다가 에러날 바엔 트레쉬 벨류가 들어가 있어도 퍼포먼스 영향없는거 확인하고 넘기는게 낫다......