개발공부

토이 프로젝트 - A - JAE GAG 페이지 구성 - 개그 풀어보기 페이지

카로루딘 2023. 8. 31. 03:10

개그의 정답을 맞추면 체크 아이콘과 함께 인정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>&lt;</span></RadioButton>
        <InjungAjaeTextBox>
          <InjungAjaeText>
            <RadioButton onClick={() => handleButtonClick('injung')} isSelected={selectedOption === 'injung'} >인정!</RadioButton>
            <span>&nbsp;VS&nbsp;</span>
            <RadioButton onClick={() => handleButtonClick('ajae')} isSelected={selectedOption === 'ajae'}>아재!</RadioButton>

          </InjungAjaeText>
          <p>선택하고 다음 개그로!</p>
        </InjungAjaeTextBox>
        <RadioButton onClick={() => moveOtherGag(gagData?.prevGagId as number)}><span>&gt;</span></RadioButton>
      </MoveOtherGagBox>}
          <p onClick={()=> {navigate('/Gaglist')}}>목록으로</p>

  </BackgroundBox>);
}
 

들어간 로직이 복잡미묘한 것에 비해 div 요소들은 생각보다 깔끔한 편이다. 리액트 훅 폼을 사용하는 Controller 부분이 좀 지저분하지만 저건 어쩔 수 없다. 괜히 건드렸다가 에러날 바엔 트레쉬 벨류가 들어가 있어도 퍼포먼스 영향없는거 확인하고 넘기는게 낫다......