diff --git a/src/app/(routes)/profile/today-quiz/page.tsx b/src/app/(routes)/profile/today-quiz/page.tsx index c57044e3..6bad4ae6 100644 --- a/src/app/(routes)/profile/today-quiz/page.tsx +++ b/src/app/(routes)/profile/today-quiz/page.tsx @@ -3,23 +3,16 @@ import InviteReward from '@/features/payment/components/invite-reward' import Icon from '@/shared/components/custom/icon' import Text from '@/shared/components/ui/text' import { getTodayQuizInfo } from '@/requests/quiz/server' +import { todayQuizCheckList } from '@/features/quiz/config' const TodayQuizPage = async () => { const { currentConsecutiveDays } = await getTodayQuizInfo() - const defaultCheckData = [ - { day: 1, isComplete: false }, - { day: 2, isComplete: false }, - { day: 3, isComplete: false }, - { day: 4, isComplete: false }, - { day: 5, isComplete: false }, - ] - - const todayCheckData = defaultCheckData.map((checkItem) => { - if (checkItem.day > currentConsecutiveDays) { - return { ...checkItem } + const todayCheckData = todayQuizCheckList.map((checkItem) => { + if (checkItem.day <= currentConsecutiveDays) { + return { ...checkItem, isComplete: true } } - return { ...checkItem, isComplete: true } + return { ...checkItem } }) return ( diff --git a/src/features/quiz/components/replay-quiz-drawer.tsx b/src/features/quiz/components/replay-quiz-drawer.tsx index 832d099e..3c790e2a 100644 --- a/src/features/quiz/components/replay-quiz-drawer.tsx +++ b/src/features/quiz/components/replay-quiz-drawer.tsx @@ -46,18 +46,13 @@ const ReplayQuizDrawer = ({ { onSuccess: (data) => router.push( - '/quiz/' + - data.quizSetId + - '?quizSetType=DOCUMENT_QUIZ_SET' + + `/quiz/${data.quizSetId}?` + + 'quizSetType=DOCUMENT_QUIZ_SET' + '&' + - 'createdAt=' + - data.createdAt + + `createdAt=${data.createdAt}&documentName=${documentName}&directoryEmoji=${directoryEmoji}` + '&' + - 'documentName=' + - documentName + - '&' + - 'directoryEmoji=' + - directoryEmoji + 'redirectUrl=' + + `/document/${documentId}` ), } ) diff --git a/src/features/quiz/config/index.tsx b/src/features/quiz/config/index.tsx index 47154f68..7bc18b70 100644 --- a/src/features/quiz/config/index.tsx +++ b/src/features/quiz/config/index.tsx @@ -102,3 +102,11 @@ export const reportOptions = [ export const QUIZ_ANIMATION_DURATION = 1 export const UNTIL_EXPLANATION_DRAWER_OPEN = 800 + +export const todayQuizCheckList = [ + { day: 1, isComplete: false }, + { day: 2, isComplete: false }, + { day: 3, isComplete: false }, + { day: 4, isComplete: false }, + { day: 5, isComplete: false }, +] diff --git a/src/features/quiz/screen/ai-creating-quiz/index.tsx b/src/features/quiz/screen/ai-creating-quiz/index.tsx index 0d6d89c5..33ea469b 100644 --- a/src/features/quiz/screen/ai-creating-quiz/index.tsx +++ b/src/features/quiz/screen/ai-creating-quiz/index.tsx @@ -75,15 +75,13 @@ const AiCreatingQuiz = ({ documentId, documentName, directoryEmoji, onError }: P createCheckQuizSetMutate(documentId, { onSuccess: (data) => { router.push( - '/quiz/' + - data.quizSetId + - '?quizSetType=FIRST_QUIZ_SET' + + `/quiz/${data.quizSetId}?` + + 'quizSetType=FIRST_QUIZ_SET' + '&' + - `createdAt=${data.createdAt}` + + `createdAt=${data.createdAt}&documentName=${documentName}&directoryEmoji=${directoryEmoji}` + '&' + - `documentName=${documentName}` + - '&' + - `directoryEmoji=${directoryEmoji}` + 'redirectUrl=' + + `/document/${documentId}` ) }, }) diff --git a/src/features/quiz/screen/quiz-result/index.tsx b/src/features/quiz/screen/quiz-result/index.tsx new file mode 100644 index 00000000..9348e9a7 --- /dev/null +++ b/src/features/quiz/screen/quiz-result/index.tsx @@ -0,0 +1,144 @@ +import Icon from '@/shared/components/custom/icon' +import Text from '@/shared/components/ui/text' +import { msToElapsedTimeKorean } from '@/shared/utils/time' +import { QuizItem } from '@/types/quiz' +import QuizCard from '../../components/quiz-card' +import { Button } from '@/shared/components/ui/button' +import FixedBottom from '@/shared/components/custom/fixed-bottom' + +interface Props { + collectQuizCount: number + quizzes: QuizItem[] + totalElapsedTime: number + showRecord: boolean + setShowRecord: (value: boolean) => void + quizResults: ({ + id: number + answer: NonNullable + choseAnswer: string + elapsedTime: number + } | null)[] + onClick: () => void +} + +const QuizResult = ({ + collectQuizCount, + quizzes, + totalElapsedTime, + showRecord, + setShowRecord, + quizResults, + onClick, +}: Props) => { + return ( +
+
+
+ +
+ 퀴즈 완료! + + {collectQuizCount}/{quizzes.length} + +
+ +
+
+
+ +
+ + 문제 수 + + + {quizzes.length} 문제 + +
+ +
+ +
+
+ +
+ + 소요시간 + + + {msToElapsedTimeKorean(totalElapsedTime)} + +
+ +
+ +
+
+ +
+ + 정답률 + + + {Math.floor((collectQuizCount / quizzes.length) * 100)}% + +
+
+
+ + {showRecord ? ( +
+ + {quizzes.length}문제 중 {collectQuizCount}문제{' '} + 맞았어요 + +
+ {quizzes.map((quiz, index) => ( + + + {quizResults[index]?.answer ? ( + 정답 + ) : ( + 오답 + )} + + + 전공 공부 {'>'} 최근 이슈 + +
+ } + userAnswer={quizResults[index]?.choseAnswer} + answerMode={true} + showExplanation={true} + /> + ))} +
+
+ ) : ( +
+ +
+ )} +
+ + + + +
+ ) +} + +export default QuizResult diff --git a/src/features/quiz/screen/quiz-view/index.tsx b/src/features/quiz/screen/quiz-view/index.tsx index 7110f69e..2539b718 100644 --- a/src/features/quiz/screen/quiz-view/index.tsx +++ b/src/features/quiz/screen/quiz-view/index.tsx @@ -14,12 +14,17 @@ import ExitDialog from './components/exit-dialog' import { useEffect, useState } from 'react' import { useParams, useRouter, useSearchParams } from 'next/navigation' import { useUpdateQuizResult } from '@/requests/quiz/hooks' -import FixedBottom from '@/shared/components/custom/fixed-bottom' -import { Button } from '@/shared/components/ui/button' -import Icon from '@/shared/components/custom/icon' -import Text from '@/shared/components/ui/text' -import QuizCard from '../../components/quiz-card' -import { msToElapsedTimeKorean } from '@/shared/utils/time' +// import FixedBottom from '@/shared/components/custom/fixed-bottom' +// import { Button } from '@/shared/components/ui/button' +// import Icon from '@/shared/components/custom/icon' +// import Text from '@/shared/components/ui/text' +// import QuizCard from '../../components/quiz-card' +// import { msToElapsedTimeKorean } from '@/shared/utils/time' +import TodayQuizReward from '../today-quiz-reward' +import { useQuery } from '@tanstack/react-query' +import { queries } from '@/shared/lib/tanstack-query/query-keys' +import QuizResult from '../quiz-result' +import { todayQuizCheckList } from '../../config' interface Props { quizzes: Quiz.Item[] @@ -30,6 +35,18 @@ const QuizView = ({ quizzes, isFirst }: Props) => { const router = useRouter() const { id } = useParams() const redirectUrl = useSearchParams().get('redirectUrl') + const quizSetType = useSearchParams().get('quizSetType') + + const { data: todayQuizInfo } = useQuery(queries.quiz.todayQuizInfo()) + const currentConsecutiveDays = todayQuizInfo?.currentConsecutiveDays ?? 0 + + const todayCheckData = todayQuizCheckList.map((checkItem) => { + if (checkItem.day <= currentConsecutiveDays) { + return { ...checkItem, isComplete: true } + } + return { ...checkItem } + }) + const { mutate: updateQuizResultMutate, isPending: isUpdatingQuizResult } = useUpdateQuizResult() const { currentIndex, navigateToNext } = useQuizNavigation() const { @@ -48,6 +65,7 @@ const QuizView = ({ quizzes, isFirst }: Props) => { const [showResult, setShowResult] = useState(false) const [showRecord, setShowRecord] = useState(false) + const [showTodayQuizReward, setShowTodayQuizReward] = useState(false) const [exitDialogOpen, setExitDialogOpen] = useState(false) @@ -104,6 +122,14 @@ const QuizView = ({ quizzes, isFirst }: Props) => { }) } + const handleClickConfirm = () => { + if (quizSetType === 'TODAY_QUIZ_SET') { + setShowTodayQuizReward(true) + setShowResult(false) + } + redirectUrl ? router.replace(redirectUrl) : router.replace('/') + } + useEffect(() => { // eslint-disable-next-line @typescript-eslint/no-unsafe-call runTimer() @@ -113,116 +139,24 @@ const QuizView = ({ quizzes, isFirst }: Props) => { if (showResult) { return ( -
-
-
- -
- 퀴즈 완료! - - {collectQuizCount}/{quizzes.length} - -
- -
-
-
- -
- - 문제 수 - - - {quizzes.length} 문제 - -
- -
- -
-
- -
- - 소요시간 - - - {msToElapsedTimeKorean(totalElapsedTime)} - -
- -
- -
-
- -
- - 정답률 - - - {Math.floor((collectQuizCount / quizzes.length) * 100)}% - -
-
-
- - {showRecord ? ( -
- - {quizzes.length}문제 중{' '} - {collectQuizCount}문제 맞았어요 - -
- {quizzes.map((quiz, index) => ( - - - {quizResults[index]?.answer ? ( - 정답 - ) : ( - 오답 - )} - - - 전공 공부 {'>'} 최근 이슈 - -
- } - userAnswer={quizResults[index]?.choseAnswer} - answerMode={true} - showExplanation={true} - /> - ))} -
-
- ) : ( -
- -
- )} -
+ + ) + } - - - -
+ if (showTodayQuizReward) { + return ( + ) } diff --git a/src/features/quiz/screen/today-quiz-reward/index.tsx b/src/features/quiz/screen/today-quiz-reward/index.tsx new file mode 100644 index 00000000..550b2f33 --- /dev/null +++ b/src/features/quiz/screen/today-quiz-reward/index.tsx @@ -0,0 +1,46 @@ +'use client' + +import Icon from '@/shared/components/custom/icon' +import Text from '@/shared/components/ui/text' +import DayCheck from '../../components/today-quiz-check/day-check' +import { Button } from '@/shared/components/ui/button' +import { useRouter } from 'next/navigation' + +interface Props { + currentConsecutiveDays: number + todayCheckData: { + day: number + isComplete: boolean + }[] +} + +// TODO: 애니메이션 구현 +const TodayQuizReward = ({ currentConsecutiveDays, todayCheckData }: Props) => { + const router = useRouter() + + return ( +
+
+ +
+ + 연속 {currentConsecutiveDays}일 완료 + + + 오늘의 퀴즈를 완료할 때마다 별 5개를 드리고,
+ 5일 연속 완료하면 20개를 드려요 +
+
+
+ + + + {/* 메인 화면으로 이동하면서 토스트 띄우기 */} + +
+ ) +} + +export default TodayQuizReward diff --git a/src/requests/quiz/client.tsx b/src/requests/quiz/client.tsx index 05e4df83..d410462a 100644 --- a/src/requests/quiz/client.tsx +++ b/src/requests/quiz/client.tsx @@ -3,7 +3,8 @@ import { API_ENDPOINTS } from '@/shared/configs/endpoint' import { http } from '@/shared/lib/axios/http' -export const fetchDirectoryQuizzes = async ({ directoryId }: { directoryId: number }) => { +/** GET GET /directories/{directory_id}/quizzes - 디렉토리에 생성된 모든 퀴즈 랜덤하게 가져오기 */ +export const getDirectoryQuizzes = async ({ directoryId }: { directoryId: number }) => { try { const { data } = await http.get( API_ENDPOINTS.QUIZ.GET.BY_DIRECTORY(directoryId) @@ -14,8 +15,8 @@ export const fetchDirectoryQuizzes = async ({ directoryId }: { directoryId: numb } } -// query-keys.ts에서 사용 중 -export const fetchDocumentQuizzes = async ({ +/** GET /documents/{document_id}/quizzes - document_id에 해당하는 모든 퀴즈 가져오기 */ +export const getDocumentQuizzes = async ({ documentId, quizType, }: { @@ -37,8 +38,8 @@ export const fetchDocumentQuizzes = async ({ } } -// query-keys.ts에서 사용 중 -export const fetchWrongAnswerQuizzes = async () => { +/** GET /incorrect-quizzes - 오답 터뜨리기 퀴즈 가져오기 */ +export const getWrongAnswerQuizzes = async () => { try { const { data } = await http.get( API_ENDPOINTS.QUIZ.GET.WRONG_ANSWER @@ -49,8 +50,8 @@ export const fetchWrongAnswerQuizzes = async () => { } } -// query-keys.ts에서 사용 중 -export const fetchQuizSetRecord = async ({ +/** GET /quizzes/{quiz_set_id}/{quiz_set_type}/quiz-record - 퀴즈 세트에 대한 상세 기록 */ +export const getQuizSetRecord = async ({ quizSetId, quizSetType, }: { @@ -67,6 +68,7 @@ export const fetchQuizSetRecord = async ({ } } +/** POST /quizzes/documents/{document_id}/check-quiz-set - 퀴즈 생성 후, 퀴즈 오류 확인을 위한 퀴즈세트 생성 */ export const createQuizSetForCheck = async ({ documentId }: { documentId: number }) => { try { const { data } = await http.post( @@ -79,6 +81,7 @@ export const createQuizSetForCheck = async ({ documentId }: { documentId: number } } +/** POST /quizzes/documents/{document_id}/custom-quiz-set - 사용자가 생성한 기존 문서에서 직접 퀴즈 세트 (다시)생성 */ export const createReplayDocumentQuizSet = async ({ documentId, requestBody, @@ -97,6 +100,7 @@ export const createReplayDocumentQuizSet = async ({ } } +/** PATCH /quiz/result - 퀴즈 결과 업데이트 */ export const updateQuizResult = async (requestBody: Quiz.Request.UpdateQuizResult) => { try { const { data } = await http.patch( @@ -109,6 +113,7 @@ export const updateQuizResult = async (requestBody: Quiz.Request.UpdateQuizResul } } +/** PATCH /quiz/result - 오답 터뜨리기 결과 업데이트 */ export const updateWrongQuizResult = async (requestBody: Quiz.Request.UpdateWrongQuizResult) => { try { const response = await http.patch(API_ENDPOINTS.QUIZ.PATCH.UPDATE_WRONG_RESULT, requestBody) @@ -119,6 +124,7 @@ export const updateWrongQuizResult = async (requestBody: Quiz.Request.UpdateWron } } +/** POST /collections/{collection_id}/collection-quizzes - 컬렉션 퀴즈 시작하기 */ export const collectionQuizzesInfo = async ({ collectionId }: { collectionId: number }) => { try { const { data } = await http.post( @@ -130,6 +136,7 @@ export const collectionQuizzesInfo = async ({ collectionId }: { collectionId: nu } } +/** GET /quizzes/quiz-records - 전체 퀴즈 기록 */ export const getQuizRecords = async () => { try { const { data } = await http.get( @@ -164,3 +171,13 @@ export const getReviewPicks = async (documentId: number) => { throw error } } + +/** GET /quiz-sets/today - 오늘의 퀴즈 세트 정보 가져오기 */ +export const getTodayQuizInfo = async () => { + try { + const { data } = await http.get(API_ENDPOINTS.QUIZ.GET.TODAY_SET) + return data + } catch (error: unknown) { + throw error + } +} diff --git a/src/requests/quiz/hooks.ts b/src/requests/quiz/hooks.ts index 65bd71dd..948870c9 100644 --- a/src/requests/quiz/hooks.ts +++ b/src/requests/quiz/hooks.ts @@ -5,7 +5,7 @@ import { collectionQuizzesInfo, createQuizSetForCheck, createReplayDocumentQuizSet, - fetchDirectoryQuizzes, + getDirectoryQuizzes, updateQuizResult, updateWrongQuizResult, } from './client' @@ -21,7 +21,7 @@ import { export const useDirectoryQuizzes = (directoryId: number | null) => { return useQuery({ queryKey: ['directoryQuizzes', directoryId], - queryFn: async () => fetchDirectoryQuizzes({ directoryId: directoryId! }), + queryFn: async () => getDirectoryQuizzes({ directoryId: directoryId! }), enabled: !!directoryId, }) } diff --git a/src/shared/lib/tanstack-query/query-keys.ts b/src/shared/lib/tanstack-query/query-keys.ts index 3275383e..45d3ff4f 100644 --- a/src/shared/lib/tanstack-query/query-keys.ts +++ b/src/shared/lib/tanstack-query/query-keys.ts @@ -39,7 +39,7 @@ export const queries = createQueryKeyStore({ quiz: { listByDocument: (params: { documentId: number; quizType?: Quiz.Type }) => ({ queryKey: [params], - queryFn: () => REQUEST.quiz.fetchDocumentQuizzes(params), + queryFn: () => REQUEST.quiz.getDocumentQuizzes(params), enabled: !!params.documentId, }), allRecords: () => ({ @@ -53,17 +53,21 @@ export const queries = createQueryKeyStore({ }), setRecord: (params: { quizSetId: string; quizSetType: Quiz.Set.Type }) => ({ queryKey: [params], - queryFn: () => REQUEST.quiz.fetchQuizSetRecord(params), + queryFn: () => REQUEST.quiz.getQuizSetRecord(params), enabled: !!params.quizSetId, }), bomb: (key?: Date) => ({ queryKey: [key], - queryFn: () => REQUEST.quiz.fetchWrongAnswerQuizzes(), + queryFn: () => REQUEST.quiz.getWrongAnswerQuizzes(), }), reviewPicks: (documentId: number) => ({ queryKey: [documentId], queryFn: () => REQUEST.quiz.getReviewPicks(documentId), }), + todayQuizInfo: () => ({ + queryKey: [''], + queryFn: () => REQUEST.quiz.getTodayQuizInfo(), + }), }, collection: {