-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #82 from Learn-and-Give/develop
Release/v1.3.0
- Loading branch information
Showing
16 changed files
with
451 additions
and
401 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import api from "../my-api"; | ||
import { getCode } from "../session-code"; | ||
interface Props { | ||
quizId: string; | ||
rate: "GOOD" | "BAD" | null; | ||
} | ||
export const updateQuizRate = async ({ quizId, rate }: Props) => { | ||
api.defaults.headers.common["code"] = getCode(); | ||
|
||
return await api.put(`/quizzes/${quizId}/rate`, { rate }); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import Link from "next/link"; | ||
import { useRouter } from "next/router"; | ||
import React, { useState } from "react"; | ||
import useChallengeDetailQuery from "../../../hooks/challenge-detail-query"; | ||
import { classNames } from "../../../styles/classname-maker"; | ||
import { QuizSummary } from "../../../types/Quiz"; | ||
interface Props { | ||
challengeId: string; | ||
quizId: string; | ||
quizzes: QuizSummary[]; | ||
} | ||
function QuizDetailNav({ challengeId, quizId, quizzes }: Props) { | ||
const { data: challengeDetail } = useChallengeDetailQuery({ challengeId }); | ||
const router = useRouter(); | ||
|
||
const isMyQuiz = router.asPath.search("my") !== -1; | ||
|
||
const [showToc, setShowToc] = useState(false); | ||
|
||
const onTocClick = () => { | ||
setShowToc((prev) => !prev); | ||
}; | ||
|
||
const onMoveQuizClick = (move: string) => { | ||
if (!quizzes) { | ||
return; | ||
} | ||
for (let i = 0; i < quizzes.length; i++) { | ||
if (String(quizzes[i].quizId) === quizId) { | ||
if (move === "prev" && i - 1 >= 0) { | ||
document.location.replace( | ||
`/challenges/${challengeId}/quizzes/${quizzes[i - 1].quizId}${ | ||
isMyQuiz ? "/my" : "" | ||
}?week=${router.asPath.split("?week=")[1]}` | ||
); | ||
} else if (move === "next" && i + 1 < quizzes.length) { | ||
document.location.replace( | ||
`/challenges/${challengeId}/quizzes/${quizzes[i + 1].quizId}${ | ||
isMyQuiz ? "/my" : "" | ||
}?week=${router.asPath.split("?week=")[1]}` | ||
); | ||
} | ||
} | ||
} | ||
}; | ||
// 퀴즈가 첫 번째 혹은 마지막 순서인지 확인한다. 두 경우에는 각각 이전과 이후로 이동하지 못한다. | ||
const isDisabled = (move: string) => { | ||
if (!quizzes) { | ||
return; | ||
} | ||
|
||
for (let i = 0; i < quizzes.length; i++) { | ||
if (String(quizzes[i].quizId) === quizId) { | ||
if (move === "prev" && i == 0) { | ||
return true; | ||
} else if (move === "next" && i == quizzes.length - 1) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
}; | ||
|
||
return ( | ||
<> | ||
<div className="fixed flex items-center justify-between gap-3 w-full left-0 bottom-0 p-3 border-t-[1px] bg-white z-10"> | ||
<button | ||
type="button" | ||
onClick={onTocClick} | ||
className="py-2 px-4 rounded bg-gray-100 text-gray-700 cursor-pointer" | ||
> | ||
문제 목록 | ||
</button> | ||
<Link href={`/challenges/${challengeId}`}> | ||
<button | ||
type="button" | ||
className="text-gray-700 cursor-pointer hover:drop-shadow" | ||
> | ||
{challengeDetail?.title} 챌린지 | ||
</button> | ||
</Link> | ||
<div className="flex gap-2"> | ||
<button | ||
onClick={() => onMoveQuizClick("prev")} | ||
className={classNames( | ||
isDisabled("prev") | ||
? "bg-gray-100 text-gray-700 cursor-default" | ||
: "bg-[#5c3cde] hover:bg-[#4026ab] text-white font-bold focus:outline-none focus:shadow-outline cursor-pointer", | ||
"py-2 px-4 rounded" | ||
)} | ||
> | ||
이전 | ||
</button> | ||
<button | ||
onClick={() => onMoveQuizClick("next")} | ||
className={classNames( | ||
isDisabled("next") | ||
? "bg-gray-200 text-gray-700 cursor-default" | ||
: "bg-[#5c3cde] hover:bg-[#4026ab] text-white font-bold focus:outline-none focus:shadow-outline cursor-pointer", | ||
"py-2 px-4 rounded" | ||
)} | ||
> | ||
다음 | ||
</button> | ||
</div> | ||
</div> | ||
{showToc && ( | ||
<div className="fixed left-0 top-0 flex flex-col gap-5 w-1/4 h-full py-32 px-10 bg-white shadow-sm border-[1px] border-gray-300 sm:w-[80%] sm:py-20 animate-in slide-in-from-left-10 duration-200"> | ||
<h2 className="font-semibold text-xl cursor-pointer">문제 목록</h2> | ||
|
||
<ul className="flex flex-col gap-1 overflow-y-scroll scrollbar-thin"> | ||
{quizzes?.map((quiz) => ( | ||
<li | ||
key={quiz.quizId} | ||
className={classNames( | ||
String(quiz.quizId) === quizId | ||
? "text-lg font-semibold text-[#5c3cde]" | ||
: "", | ||
"flex items-center gap-2" | ||
)} | ||
onClick={() => setShowToc(false)} | ||
> | ||
<button | ||
onClick={() => { | ||
document.location.replace( | ||
`/challenges/${challengeId}/quizzes/${quiz.quizId}${ | ||
isMyQuiz ? "/my" : "" | ||
}?week=${router.asPath.split("?week=")[1]}` | ||
); | ||
}} | ||
> | ||
{quiz.quizTitle} | ||
</button> | ||
<span className="badge badge-secondary font-normal"> | ||
{quiz.quizWeek}주차 | ||
</span> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
)} | ||
</> | ||
); | ||
} | ||
|
||
export default QuizDetailNav; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Icon } from "@iconify/react"; | ||
import { useMutation, useQueryClient } from "@tanstack/react-query"; | ||
import React from "react"; | ||
import { updateQuizRate } from "../../../api/quizzes/quiz-rate"; | ||
import useQuizDetailQuery from "../../../hooks/quiz-detail"; | ||
import { QuizDetailSelect } from "../../../types/Quiz"; | ||
interface Props { | ||
quizId: string; | ||
} | ||
interface QuizRate { | ||
rate: "GOOD" | "BAD" | null; | ||
} | ||
function QuizRate({ quizId }: Props) { | ||
const queryClient = useQueryClient(); | ||
const { data: quizDetail } = useQuizDetailQuery(); | ||
const { mutate: mutateQuizRate } = useMutation( | ||
({ rate }: QuizRate) => updateQuizRate({ quizId: quizId, rate }), | ||
{ | ||
onMutate: async (data) => { | ||
await queryClient.cancelQueries(["quizDetail", quizId]); | ||
|
||
const previousQuizDetail = queryClient.getQueryData<QuizDetailSelect>([ | ||
"quizDetail", | ||
quizId, | ||
]); | ||
|
||
if (previousQuizDetail) { | ||
queryClient.setQueryData<QuizDetailSelect>(["quizDetail", quizId], { | ||
...previousQuizDetail, | ||
didIRate: data.rate, | ||
cntOfGood: | ||
previousQuizDetail.didIRate === "GOOD" | ||
? previousQuizDetail.cntOfGood - 1 | ||
: data.rate === "GOOD" | ||
? previousQuizDetail.cntOfGood + 1 | ||
: previousQuizDetail.cntOfGood, | ||
}); | ||
} | ||
|
||
return { previousQuizDetail }; | ||
}, | ||
onError: (err, newQuizDetail, context) => { | ||
queryClient.setQueryData(["quizDetail", quizId], quizDetail); | ||
}, | ||
onSettled: () => { | ||
queryClient.invalidateQueries(["quizDetail", quizId]); | ||
}, | ||
} | ||
); | ||
|
||
const onRateClick = ({ rate }: QuizRate) => { | ||
mutateQuizRate({ rate }); | ||
}; | ||
|
||
return ( | ||
<div className="flex items-start gap-4"> | ||
<span className="flex items-center gap-1"> | ||
{quizDetail?.didIRate === "GOOD" ? ( | ||
<button onClick={() => onRateClick({ rate: null })}> | ||
<Icon icon="icon-park-solid:good-two" height={22} /> | ||
</button> | ||
) : ( | ||
<button onClick={() => onRateClick({ rate: "GOOD" })}> | ||
<Icon icon="icon-park-outline:good-two" height={22} /> | ||
</button> | ||
)} | ||
<span className="text-sm"> | ||
{quizDetail?.cntOfGood ? quizDetail?.cntOfGood : 0} | ||
</span> | ||
</span> | ||
{quizDetail?.didIRate === "BAD" ? ( | ||
<button onClick={() => onRateClick({ rate: null })} className="pt-1"> | ||
<Icon icon="icon-park-solid:bad-two" height={22} /> | ||
</button> | ||
) : ( | ||
<button onClick={() => onRateClick({ rate: "BAD" })} className="pt-1"> | ||
<Icon icon="icon-park-outline:bad-two" height={22} /> | ||
</button> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
export default QuizRate; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { AxiosError } from "axios"; | ||
import { useRouter } from "next/router"; | ||
import { fetchQuizDetail } from "../api/quizzes/quiz-detail"; | ||
import { QuizDetail, QuizDetailSelect } from "../types/Quiz"; | ||
export default function useQuizDetailQuery() { | ||
const router = useRouter(); | ||
const quizId = String(router.query.qid); | ||
return useQuery<QuizDetail, AxiosError, QuizDetailSelect>( | ||
["quizDetail", quizId], | ||
() => fetchQuizDetail({ quizId }), | ||
{ | ||
enabled: !!router.query.qid, | ||
onError: (err) => {}, | ||
select: (data) => { | ||
return { ...data, quizRubric: JSON.parse(data.quizRubric) }; | ||
}, | ||
} | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.