Skip to content

Commit

Permalink
퀴즈 솔루션 탭 (#54)
Browse files Browse the repository at this point in the history
* feat: 질문 탭에 준비중인 기능이라는 텍스트 표시

* refactor: Quiz Submission 테이블 관련 API 분리

* feat: 특정 유저의 특정 퀴즈에 대한 제출 정보 가져오는 로직 작성

* feat: 솔루션 페이지 컨텐츠 접근 제한 로직 작성

* fix: 퀴즈 제출 내용 변경시 아직 화면에 반영되지 않는 오류

* refactor: getSubmittedQuiz 재 추가

* style: detailOfQuiz 로 이름 변경
  • Loading branch information
bbearcookie authored Dec 29, 2023
1 parent 44f5c70 commit 4be0479
Show file tree
Hide file tree
Showing 14 changed files with 153 additions and 58 deletions.
11 changes: 9 additions & 2 deletions app/quizzes/[id]/_components/choice-form.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<HTMLFormElement>) => {
e.preventDefault();
Expand All @@ -33,6 +38,8 @@ export default function ChoiceForm({ quizId, children }: ChoiceFormProps) {
return;
}

router.refresh();

submitQuiz(
{
quizId,
Expand Down
13 changes: 0 additions & 13 deletions app/quizzes/[id]/answers/layout.tsx

This file was deleted.

3 changes: 0 additions & 3 deletions app/quizzes/[id]/answers/loading.tsx

This file was deleted.

37 changes: 29 additions & 8 deletions app/quizzes/[id]/answers/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import quizOptions from '@/services/quiz/options';
import quizSubmissionOptions from '@/services/quiz-submission/options';
import {
HydrationBoundary,
QueryClient,
Expand All @@ -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();
Expand All @@ -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 (
<HydrationBoundary state={dehydrate(queryClient)}>
<QuizAnswer quizId={quizId} />
<Separator className="my-4" />
<Comments disable={!session} quizId={quizId} userId={session?.user.id} />
</HydrationBoundary>
);
if (submission?.success) {
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<QuizAnswer quizId={quizId} />
<Separator className="my-4" />
<Comments
disable={!session}
quizId={quizId}
userId={session?.user.id}
/>
</HydrationBoundary>
);
} else {
return (
<div className="flex flex-col items-center gap-6">
<h2 className="text-xl font-semibold">퀴즈를 먼저 맞춰주세요!</h2>
<Link replace href={`/quizzes/${quizId}`}>
<Button>퀴즈로 이동하기</Button>
</Link>
</div>
);
}
}
11 changes: 9 additions & 2 deletions app/quizzes/[id]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,19 @@ export default function Layout({ children, params }: LayoutProps) {

<section className="p-4">
<article className="mb-4 flex w-full">
<NavLink className="grow" href={`/quizzes/${quizId}`}>
<NavLink className="grow" replace href={`/quizzes/${quizId}`}>
퀴즈
</NavLink>
<NavLink className="grow" href={`/quizzes/${quizId}/questions`}>
<NavLink
className="grow"
replace
href={`/quizzes/${quizId}/questions`}
>
질문
</NavLink>
<NavLink className="grow" replace href={`/quizzes/${quizId}/answers`}>
솔루션
</NavLink>
</article>
{children}
</section>
Expand Down
2 changes: 1 addition & 1 deletion app/quizzes/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default async function Page({ params }: { params: { id: string } }) {
<AccordionTrigger>힌트 보기</AccordionTrigger>
<AccordionContent className="flex flex-wrap gap-1">
{hints?.map((hint) => (
<Badge key={hint.id} variant="secondary">
<Badge key={hint.id} variant="default">
{hint.description}
</Badge>
))}
Expand Down
9 changes: 9 additions & 0 deletions app/quizzes/[id]/questions/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import LoadingSpinner from '@/components/common/loading-spinner/loading-spinner';

export default function Loading() {
return (
<section className="flex min-h-[90vh] items-center justify-center overflow-hidden">
<LoadingSpinner size="3xl" className="text-blue-primary" />
</section>
);
}
14 changes: 12 additions & 2 deletions app/quizzes/[id]/questions/page.tsx
Original file line number Diff line number Diff line change
@@ -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 <section>{quizId}번 퀴즈에 대한 질문 페이지</section>;
return (
<div className="flex flex-col items-center gap-6">
<h2 className="text-xl font-semibold">준비중인 기능이에요!</h2>
<Link replace href={`/quizzes/${quizId}`}>
<Button>퀴즈로 이동하기</Button>
</Link>
</div>
);
}
47 changes: 47 additions & 0 deletions services/quiz-submission/api.ts
Original file line number Diff line number Diff line change
@@ -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<Database> = 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<Database> = 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;
17 changes: 17 additions & 0 deletions services/quiz-submission/hook.ts
Original file line number Diff line number Diff line change
@@ -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,
});
}
19 changes: 19 additions & 0 deletions services/quiz-submission/options.ts
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 0 additions & 17 deletions services/quiz/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
10 changes: 0 additions & 10 deletions services/quiz/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions services/quiz/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down

0 comments on commit 4be0479

Please sign in to comment.