From 4be0479187f2c2ba0fdac16d4ea8f8a34e39285d Mon Sep 17 00:00:00 2001 From: SangHoon Lee <50488780+bbearcookie@users.noreply.github.com> Date: Fri, 29 Dec 2023 17:18:15 +0900 Subject: [PATCH] =?UTF-8?q?=ED=80=B4=EC=A6=88=20=EC=86=94=EB=A3=A8?= =?UTF-8?q?=EC=85=98=20=ED=83=AD=20(#54)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 질문 탭에 준비중인 기능이라는 텍스트 표시 * refactor: Quiz Submission 테이블 관련 API 분리 * feat: 특정 유저의 특정 퀴즈에 대한 제출 정보 가져오는 로직 작성 * feat: 솔루션 페이지 컨텐츠 접근 제한 로직 작성 * fix: 퀴즈 제출 내용 변경시 아직 화면에 반영되지 않는 오류 * refactor: getSubmittedQuiz 재 추가 * style: detailOfQuiz 로 이름 변경 --- app/quizzes/[id]/_components/choice-form.tsx | 11 ++++- app/quizzes/[id]/answers/layout.tsx | 13 ------ app/quizzes/[id]/answers/loading.tsx | 3 -- app/quizzes/[id]/answers/page.tsx | 37 +++++++++++---- app/quizzes/[id]/layout.tsx | 11 ++++- app/quizzes/[id]/page.tsx | 2 +- app/quizzes/[id]/questions/loading.tsx | 9 ++++ app/quizzes/[id]/questions/page.tsx | 14 +++++- services/quiz-submission/api.ts | 47 ++++++++++++++++++++ services/quiz-submission/hook.ts | 17 +++++++ services/quiz-submission/options.ts | 19 ++++++++ services/quiz/api.ts | 17 ------- services/quiz/hooks.ts | 10 ----- services/quiz/options.ts | 1 + 14 files changed, 153 insertions(+), 58 deletions(-) delete mode 100644 app/quizzes/[id]/answers/layout.tsx delete mode 100644 app/quizzes/[id]/answers/loading.tsx create mode 100644 app/quizzes/[id]/questions/loading.tsx create mode 100644 services/quiz-submission/api.ts create mode 100644 services/quiz-submission/hook.ts create mode 100644 services/quiz-submission/options.ts diff --git a/app/quizzes/[id]/_components/choice-form.tsx b/app/quizzes/[id]/_components/choice-form.tsx index bf5a06b..24b9ddc 100644 --- a/app/quizzes/[id]/_components/choice-form.tsx +++ b/app/quizzes/[id]/_components/choice-form.tsx @@ -1,7 +1,8 @@ 'use client'; import React, { useState } from 'react'; -import { useGetChoicesOfQuiz, useSubmitQuiz } from '@/services/quiz/hooks'; +import { useGetChoicesOfQuiz } from '@/services/quiz/hooks'; +import { useSubmitQuizSubmission } from '@/services/quiz-submission/hook'; import { useRouter } from 'next/navigation'; import Button from '@/components/common/buttons/button'; import LoadingSpinner from '@/components/common/loading-spinner/loading-spinner'; @@ -17,7 +18,11 @@ export default function ChoiceForm({ quizId, children }: ChoiceFormProps) { const router = useRouter(); const { data: choices } = useGetChoicesOfQuiz(quizId); - const { mutate: submitQuiz, isPending, isSuccess } = useSubmitQuiz(); + const { + mutate: submitQuiz, + isPending, + isSuccess, + } = useSubmitQuizSubmission(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -33,6 +38,8 @@ export default function ChoiceForm({ quizId, children }: ChoiceFormProps) { return; } + router.refresh(); + submitQuiz( { quizId, diff --git a/app/quizzes/[id]/answers/layout.tsx b/app/quizzes/[id]/answers/layout.tsx deleted file mode 100644 index 5f9d53a..0000000 --- a/app/quizzes/[id]/answers/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; - -type LayoutProps = { - children: React.ReactNode; -}; - -export default function Layout({ children }: LayoutProps) { - return ( - <> -
{children}
- - ); -} diff --git a/app/quizzes/[id]/answers/loading.tsx b/app/quizzes/[id]/answers/loading.tsx deleted file mode 100644 index 5f62160..0000000 --- a/app/quizzes/[id]/answers/loading.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Loading() { - return
loading...
; -} diff --git a/app/quizzes/[id]/answers/page.tsx b/app/quizzes/[id]/answers/page.tsx index 7ae1440..4ab23b5 100644 --- a/app/quizzes/[id]/answers/page.tsx +++ b/app/quizzes/[id]/answers/page.tsx @@ -1,4 +1,5 @@ import quizOptions from '@/services/quiz/options'; +import quizSubmissionOptions from '@/services/quiz-submission/options'; import { HydrationBoundary, QueryClient, @@ -10,6 +11,8 @@ import { cookies } from 'next/headers'; import { createClient } from '@/utils/supabase/server'; import { Separator } from '@/components/ui/separator'; import commentOptions from '@/services/comment/options'; +import Link from 'next/link'; +import Button from '@/components/common/buttons/button'; export default async function Page({ params }: { params: { id: string } }) { const queryClient = new QueryClient(); @@ -22,16 +25,34 @@ export default async function Page({ params }: { params: { id: string } }) { data: { session }, } = await supabase.auth.getSession(); - await Promise.all([ + const [submission] = await Promise.all([ + queryClient.fetchQuery( + quizSubmissionOptions.detailOfQuiz(session?.user.id ?? '', quizId) + ), queryClient.prefetchQuery(quizOptions.answers(quizId)), queryClient.prefetchQuery(commentOptions.quiz(quizId)), ]); - return ( - - - - - - ); + if (submission?.success) { + return ( + + + + + + ); + } else { + return ( +
+

퀴즈를 먼저 맞춰주세요!

+ + + +
+ ); + } } diff --git a/app/quizzes/[id]/layout.tsx b/app/quizzes/[id]/layout.tsx index db298e5..9844bb3 100644 --- a/app/quizzes/[id]/layout.tsx +++ b/app/quizzes/[id]/layout.tsx @@ -18,12 +18,19 @@ export default function Layout({ children, params }: LayoutProps) {
- + 퀴즈 - + 질문 + + 솔루션 +
{children}
diff --git a/app/quizzes/[id]/page.tsx b/app/quizzes/[id]/page.tsx index 9522746..b28f4e7 100644 --- a/app/quizzes/[id]/page.tsx +++ b/app/quizzes/[id]/page.tsx @@ -51,7 +51,7 @@ export default async function Page({ params }: { params: { id: string } }) { 힌트 보기 {hints?.map((hint) => ( - + {hint.description} ))} diff --git a/app/quizzes/[id]/questions/loading.tsx b/app/quizzes/[id]/questions/loading.tsx new file mode 100644 index 0000000..e4b9bc6 --- /dev/null +++ b/app/quizzes/[id]/questions/loading.tsx @@ -0,0 +1,9 @@ +import LoadingSpinner from '@/components/common/loading-spinner/loading-spinner'; + +export default function Loading() { + return ( +
+ +
+ ); +} diff --git a/app/quizzes/[id]/questions/page.tsx b/app/quizzes/[id]/questions/page.tsx index 08ad9ea..ff80983 100644 --- a/app/quizzes/[id]/questions/page.tsx +++ b/app/quizzes/[id]/questions/page.tsx @@ -1,5 +1,15 @@ -export default async function Page({ params }: { params: { id: string } }) { +import Link from 'next/link'; +import Button from '@/components/common/buttons/button'; + +export default function Page({ params }: { params: { id: string } }) { const quizId = Number(params.id) ?? 0; - return
{quizId}번 퀴즈에 대한 질문 페이지
; + return ( +
+

준비중인 기능이에요!

+ + + +
+ ); } diff --git a/services/quiz-submission/api.ts b/services/quiz-submission/api.ts new file mode 100644 index 0000000..33c5119 --- /dev/null +++ b/services/quiz-submission/api.ts @@ -0,0 +1,47 @@ +import { createClient } from '@/utils/supabase/client'; +import { SupabaseClient } from '@supabase/supabase-js'; + +const quizSubmissionAPI = { + getSubmissionsOfUser: async (userId: string) => { + const supabase: SupabaseClient = createClient(); + + const { data } = await supabase + .from('quizsubmissions') + .select(`*, quizzes (id, *)`) + .eq('user_id', userId); + + return data; + }, + + getSubmissionOfUser: async (userId: string, quizId: number) => { + const supabase: SupabaseClient = createClient(); + + const { data } = await supabase + .from('quizsubmissions') + .select(`*, quizzes (id, *)`) + .match({ user_id: userId, quiz_id: quizId }) + .limit(1) + .single(); + + return data; + }, + + postSubmission: async (params: { quizId: number; choiceId: number }) => { + const { quizId, choiceId } = params; + + const res = await fetch('/api/quiz-submission', { + method: 'POST', + body: JSON.stringify({ quizId, choiceId }), + }); + + const json = await res.json(); + + if (!res.ok) { + throw new Error(json.error); + } + + return json; + }, +}; + +export default quizSubmissionAPI; diff --git a/services/quiz-submission/hook.ts b/services/quiz-submission/hook.ts new file mode 100644 index 0000000..69d6c05 --- /dev/null +++ b/services/quiz-submission/hook.ts @@ -0,0 +1,17 @@ +import { useSuspenseQuery, useMutation } from '@tanstack/react-query'; +import quizOptions from './options'; +import quizSubmissionAPI from './api'; + +export function useGetQuizSubmissions(userId: string) { + return useSuspenseQuery(quizOptions.all(userId)); +} + +export function useGetQuizSubmission(userId: string, quizId: number) { + return useSuspenseQuery(quizOptions.detailOfQuiz(userId, quizId)); +} + +export function useSubmitQuizSubmission() { + return useMutation({ + mutationFn: quizSubmissionAPI.postSubmission, + }); +} diff --git a/services/quiz-submission/options.ts b/services/quiz-submission/options.ts new file mode 100644 index 0000000..2413a98 --- /dev/null +++ b/services/quiz-submission/options.ts @@ -0,0 +1,19 @@ +import { queryOptions } from '@tanstack/react-query'; +import quizSubmissionAPI from './api'; + +const quizSubmissionOptions = { + default: ['quizsubmissions'] as const, + + all: (userId: string) => + queryOptions({ + queryKey: [...quizSubmissionOptions.default, userId], + queryFn: () => quizSubmissionAPI.getSubmissionsOfUser(userId), + }), + + detailOfQuiz: (userId: string, quizId: number) => ({ + queryKey: [...quizSubmissionOptions.default, userId, quizId], + queryFn: () => quizSubmissionAPI.getSubmissionOfUser(userId, quizId), + }), +}; + +export default quizSubmissionOptions; diff --git a/services/quiz/api.ts b/services/quiz/api.ts index 4a380ac..d3e79b6 100644 --- a/services/quiz/api.ts +++ b/services/quiz/api.ts @@ -150,23 +150,6 @@ const quizAPI = { return data; }, - - postQuizSubmission: async (params: { quizId: number; choiceId: number }) => { - const { quizId, choiceId } = params; - - const res = await fetch('/api/quiz-submission', { - method: 'POST', - body: JSON.stringify({ quizId, choiceId }), - }); - - const json = await res.json(); - - if (!res.ok) { - throw new Error(json.error); - } - - return json; - }, }; export default quizAPI; diff --git a/services/quiz/hooks.ts b/services/quiz/hooks.ts index e6c9a2c..0002552 100644 --- a/services/quiz/hooks.ts +++ b/services/quiz/hooks.ts @@ -10,10 +10,6 @@ export function useGetQuiz(quizId: number) { return useSuspenseQuery(quizOptions.detail(quizId)); } -export function useGetSubmittedQuiz(userId: string) { - return useSuspenseQuery(quizOptions.submitted(userId)); -} - export function useGetChoicesOfQuiz(quizId: number) { return useSuspenseQuery(quizOptions.choices(quizId)); } @@ -22,12 +18,6 @@ export function useGetAnswersOfQuiz(quizId: number) { return useSuspenseQuery(quizOptions.answers(quizId)); } -export function useSubmitQuiz() { - return useMutation({ - mutationFn: quizAPI.postQuizSubmission, - }); -} - export function useCreateQuiz() { return useMutation({ mutationFn: quizAPI.postQuiz, diff --git a/services/quiz/options.ts b/services/quiz/options.ts index 4bb43f8..474a790 100644 --- a/services/quiz/options.ts +++ b/services/quiz/options.ts @@ -33,6 +33,7 @@ const quizOptions = { queryKey: [...quizOptions.detail(quizId).queryKey, 'hints'], queryFn: () => quizAPI.getHintsOfQuiz(quizId), }), + answers: (quizId: number) => queryOptions({ queryKey: [...quizOptions.detail(quizId).queryKey, 'answers'],