diff --git a/src/app/(routes)/quiz/[id]/layout.tsx b/src/app/(routes)/quiz/[id]/layout.tsx index e73444d8..149dc506 100644 --- a/src/app/(routes)/quiz/[id]/layout.tsx +++ b/src/app/(routes)/quiz/[id]/layout.tsx @@ -1,4 +1,4 @@ -import { FunctionComponent, PropsWithChildren, Suspense } from 'react' +import { FunctionComponent, PropsWithChildren } from 'react' import { Metadata } from 'next' export const metadata: Metadata = {} @@ -6,11 +6,7 @@ export const metadata: Metadata = {} interface LayoutProps extends PropsWithChildren {} const Layout: FunctionComponent = ({ children }) => { - return ( -
- {children} -
- ) + return
{children}
} export default Layout diff --git a/src/app/(routes)/quiz/[id]/page.tsx b/src/app/(routes)/quiz/[id]/page.tsx index d0d84e6c..3b05e63b 100644 --- a/src/app/(routes)/quiz/[id]/page.tsx +++ b/src/app/(routes)/quiz/[id]/page.tsx @@ -1,6 +1,6 @@ +'use server' + import IntroAndQuizView from '@/features/quiz/screen/intro-and-quiz-view' -import { getQuizSetTypeEnum } from '@/features/quiz/utils' -import { getCollectionInfo } from '@/requests/collection/server' import { getQuizSetById } from '@/requests/quiz/server' import { notFound } from 'next/navigation' @@ -9,12 +9,12 @@ interface Props { id: string } searchParams: { - quizType: 'today' | 'document' | 'collection' | 'create' + quizSetType: Quiz.Set.Type createdAt: string // 문서 퀴즈일 경우 documentName?: string directoryEmoji?: string - // 콜렉션 퀴즈일 경우 (id는 params로 받음) + // 콜렉션 퀴즈일 경우 collectionName?: string collectionEmoji?: string } @@ -22,7 +22,7 @@ interface Props { const QuizDetailPage = async ({ params, searchParams }: Props) => { const { - quizType = 'today', + quizSetType = 'TODAY_QUIZ_SET', createdAt, documentName, directoryEmoji, @@ -30,22 +30,13 @@ const QuizDetailPage = async ({ params, searchParams }: Props) => { collectionEmoji, } = searchParams - // const isTodayQuiz = quizType === 'today' - // const isDocumentQuiz = quizType === 'document' - // const isCollectionQuiz = quizType === 'collection' - const collectionId = Number(params.id) - // const collection = isCollectionQuiz ? await getCollectionInfo(collectionId) : null - // const isCreateQuiz = quizType === 'create' - const quizSet = await getQuizSetById({ quizSetId: params.id, - collectionId: quizType === 'collection' ? Number(collectionId) : undefined, - quizSetType: getQuizSetTypeEnum(quizType), + quizSetType, }) const hasDocumentInfo = documentName !== undefined && directoryEmoji !== undefined - const hasCollectionInfo = - collectionId !== undefined && collectionName !== undefined && collectionEmoji !== undefined + const hasCollectionInfo = collectionName !== undefined && collectionEmoji !== undefined const documentInfo = hasDocumentInfo ? { name: documentName, directoryEmoji: directoryEmoji } @@ -60,7 +51,7 @@ const QuizDetailPage = async ({ params, searchParams }: Props) => { return ( { + const router = useRouter() + const { user } = useUser() const { data: collectionData } = useCollectionInfo(id) + const { mutate: createQuizSetMutate } = useCollectionQuizzesInfo() const quizCounts = useMemo(() => { if (!collectionData?.quizzes) return { multiple: 0, ox: 0 } @@ -37,6 +41,22 @@ const DetailInfo = ({ id }: Props) => { const isMine = user?.id === collectionData?.member.creatorId + const handleQuizStart = () => { + if (!collectionData) { + return + } + + createQuizSetMutate( + { collectionId: id }, + { + onSuccess: ({ quizSetId, quizSetType }) => + router.push( + `/quiz/${quizSetId}?quizSetType=${quizSetType}&collectionName=${collectionData.name}&collectionEmoji=${collectionData.emoji}` + ), + } + ) + } + /** TODO: Spinner로 대체 */ if (!collectionData) return @@ -98,13 +118,14 @@ const DetailInfo = ({ id }: Props) => { )} - - {/* 이동 /quiz/[id] - searchParams로 collectionId, createdAt, collectionName, collectionEmoji 넣어서 */} - - + > */} + {/* 이동 /quiz/[id] - searchParams로 collectionId, createdAt, collectionName, collectionEmoji 넣어서 */} + ) diff --git a/src/features/quiz/components/quiz-card/index.tsx b/src/features/quiz/components/quiz-card/index.tsx index d11a3b28..a04bef81 100644 --- a/src/features/quiz/components/quiz-card/index.tsx +++ b/src/features/quiz/components/quiz-card/index.tsx @@ -6,7 +6,7 @@ import { cn } from '@/shared/lib/utils' import React, { useState } from 'react' interface Props { - quiz: Quiz.Item + quiz: Quiz.Item | Omit header: React.ReactNode answerMode?: boolean showExplanation?: boolean diff --git a/src/features/quiz/screen/intro-and-quiz-view.tsx b/src/features/quiz/screen/intro-and-quiz-view.tsx index e6e3adec..ff966549 100644 --- a/src/features/quiz/screen/intro-and-quiz-view.tsx +++ b/src/features/quiz/screen/intro-and-quiz-view.tsx @@ -5,15 +5,15 @@ import QuizView from './quiz-view' import QuizIntro from './intro' interface Props { - quizType: 'today' | 'document' | 'collection' | 'create' - quizzes: QuizWithMetadata[] + quizSetType: Quiz.Set.Type + quizzes: Quiz.Item[] createdAt: string documentInfo?: { name: string; directoryEmoji: string } collectionInfo?: { name: string; emoji: string } } const IntroAndQuizView = ({ - quizType, + quizSetType, quizzes, createdAt, documentInfo, @@ -32,7 +32,7 @@ const IntroAndQuizView = ({ if (!finishedIntro) { return ( + return } export default IntroAndQuizView diff --git a/src/features/quiz/screen/intro/index.tsx b/src/features/quiz/screen/intro/index.tsx index 3896b7ca..9d8ea40a 100644 --- a/src/features/quiz/screen/intro/index.tsx +++ b/src/features/quiz/screen/intro/index.tsx @@ -7,7 +7,7 @@ import CollectionQuizIntro from './components/collection-quiz-intro' import { formatDateKorean } from '@/shared/utils/date' interface Props { - quizType: 'today' | 'document' | 'collection' | 'create' + quizSetType: Quiz.Set.Type createdAt: string documentInfo?: { name: string; directoryEmoji: string } collectionInfo?: { name: string; emoji: string } @@ -15,7 +15,7 @@ interface Props { } const QuizIntro = ({ - quizType, + quizSetType, createdAt, documentInfo, collectionInfo, @@ -25,13 +25,13 @@ const QuizIntro = ({ return ( ), - document: ( + DOCUMENT_QUIZ_SET: ( ), - collection: ( + COLLECTION_QUIZ_SET: ( { + try { + const { data } = await http.post( + API_ENDPOINTS.QUIZ.POST.COLLECTION(collectionId) + ) + return data + } catch (error: unknown) { + throw error + } +} diff --git a/src/requests/quiz/hooks.ts b/src/requests/quiz/hooks.ts index 83a2330c..89558d38 100644 --- a/src/requests/quiz/hooks.ts +++ b/src/requests/quiz/hooks.ts @@ -2,6 +2,7 @@ import { useMutation, useQuery } from '@tanstack/react-query' import { + collectionQuizzesInfo, createQuizSetForCheck, createReplayDocumentQuizSet, fetchDirectoryQuizzes, @@ -60,3 +61,9 @@ export const useUpdateWrongQuizResult = () => { }, }) } + +export const useCollectionQuizzesInfo = () => { + return useMutation({ + mutationFn: collectionQuizzesInfo, + }) +} diff --git a/src/requests/quiz/server.tsx b/src/requests/quiz/server.tsx index 9a6c0ed6..e9635968 100644 --- a/src/requests/quiz/server.tsx +++ b/src/requests/quiz/server.tsx @@ -16,22 +16,18 @@ export const getTodayQuizSetId = async () => { export const getQuizSetById = async ({ quizSetId, - collectionId, quizSetType, }: { quizSetId: string - collectionId?: number quizSetType: Quiz.Set.Type }) => { - const params = collectionId - ? { 'collection-id': collectionId, 'quiz-set-type': quizSetType } - : { 'quiz-set-type': quizSetType } - try { const { data } = await httpServer.get( API_ENDPOINTS.QUIZ.GET.BY_SET_ID(quizSetId), { - params, + params: { + 'quiz-set-type': quizSetType, + }, } ) return data diff --git a/src/shared/configs/endpoint.ts b/src/shared/configs/endpoint.ts index b0af8dbe..563be5cc 100644 --- a/src/shared/configs/endpoint.ts +++ b/src/shared/configs/endpoint.ts @@ -171,6 +171,9 @@ export const API_ENDPOINTS = { DOWNLOAD: (documentId: number) => `/documents/${documentId}/download-quiz`, /** GET /incorrect-quizzes - 오답 터뜨리기 퀴즈 가져오기 */ WRONG_ANSWER: '/incorrect-quizzes', + /** POST /api/v2/collections/{collection_id}/collection-quizzes - 컬렉션 퀴즈 시작하기 응답 */ + COLLECTION_QUIZZES: (collectionId: number) => + `/collections/${collectionId}/collection-quizzes`, }, PATCH: { /** PATCH /quiz/result - 퀴즈 결과 업데이트 */ diff --git a/src/types/quiz.d.ts b/src/types/quiz.d.ts index 8206a858..9c82be8e 100644 --- a/src/types/quiz.d.ts +++ b/src/types/quiz.d.ts @@ -21,8 +21,6 @@ declare global { */ declare namespace Quiz { type Item = QuizItem - type List = QuizItem[] - type ItemWithMetadata = QuizWithMetadata type ReplayType = ReplayQuizType type Type = QuizType @@ -159,6 +157,13 @@ declare global { type CreateQuizSet = DeepRequired< paths['/api/v2/quizzes/documents/{document_id}/check-quiz-set']['post']['responses']['201']['content']['application/json;charset=UTF-8'] > + + /** POST /api/v2/collections/{collection_id}/collection-quizzes + * 컬렉션 퀴즈 시작하기 응답 + */ + type StartCollectionQuiz = DeepRequired< + paths['/api/v2/collections/{collection_id}/collection-quizzes']['post']['responses']['201']['content']['application/json;charset=UTF-8'] + > } } } diff --git a/src/types/schema.d.ts b/src/types/schema.d.ts index d4a2886e..30a3fda5 100644 --- a/src/types/schema.d.ts +++ b/src/types/schema.d.ts @@ -1440,7 +1440,11 @@ export interface components { id?: number; question?: string; answer?: string; + /** Format: int64 */ + documentId?: number; documentName?: string; + /** @enum {string} */ + documentType?: "FILE" | "TEXT" | "NOTION"; directoryName?: string; }; IntegratedSearchResponse: { @@ -1496,7 +1500,11 @@ export interface components { id?: number; question?: string; answer?: string; + /** Format: int64 */ + documentId?: number; documentName?: string; + /** @enum {string} */ + documentType?: "FILE" | "TEXT" | "NOTION"; directoryName?: string; }; SearchDocumentResponse: { @@ -1723,10 +1731,10 @@ export interface components { parent?: components["schemas"]["ApplicationContext"]; id?: string; displayName?: string; - autowireCapableBeanFactory?: components["schemas"]["AutowireCapableBeanFactory"]; applicationName?: string; /** Format: int64 */ startupDate?: number; + autowireCapableBeanFactory?: components["schemas"]["AutowireCapableBeanFactory"]; environment?: components["schemas"]["Environment"]; /** Format: int32 */ beanDefinitionCount?: number; @@ -1851,8 +1859,8 @@ export interface components { is3xxRedirection?: boolean; }; JspConfigDescriptor: { - taglibs?: components["schemas"]["TaglibDescriptor"][]; jspPropertyGroups?: components["schemas"]["JspPropertyGroupDescriptor"][]; + taglibs?: components["schemas"]["TaglibDescriptor"][]; }; JspPropertyGroupDescriptor: { defaultContentType?: string; @@ -1861,11 +1869,11 @@ export interface components { errorOnELNotFound?: string; pageEncoding?: string; scriptingInvalid?: string; - isXml?: string; includePreludes?: string[]; includeCodas?: string[]; trimDirectiveWhitespaces?: string; errorOnUndeclaredNamespace?: string; + isXml?: string; buffer?: string; urlPatterns?: string[]; }; @@ -1892,13 +1900,13 @@ export interface components { hosts?: string[]; redirectView?: boolean; propagateQueryProperties?: boolean; - attributesCSV?: string; attributesMap?: { [key: string]: Record; }; attributes?: { [key: string]: string; }; + attributesCSV?: string; }; ServletContext: { sessionCookieConfig?: components["schemas"]["SessionCookieConfig"]; @@ -1933,6 +1941,7 @@ export interface components { servletRegistrations?: { [key: string]: components["schemas"]["ServletRegistration"]; }; + defaultSessionTrackingModes?: ("COOKIE" | "URL" | "SSL")[]; /** Format: int32 */ effectiveMajorVersion?: number; /** Format: int32 */ @@ -1942,7 +1951,6 @@ export interface components { filterRegistrations?: { [key: string]: components["schemas"]["FilterRegistration"]; }; - defaultSessionTrackingModes?: ("COOKIE" | "URL" | "SSL")[]; effectiveSessionTrackingModes?: ("COOKIE" | "URL" | "SSL")[]; jspConfigDescriptor?: components["schemas"]["JspConfigDescriptor"]; requestCharacterEncoding?: string;