Skip to content

Commit

Permalink
퀴즈 페이지에서 힌트 기능 추가 (#36)
Browse files Browse the repository at this point in the history
* chore: shadcn/badge 추가

* feat: 힌트 보여주는 ui 작성

* feat: 힌트 데이터 적용

* chore: 변경된 데이터베이스 타입 적용

* style: 콘솔 메시지 제거

* feat: 힌트 없으면 아코디언 숨김 처리

* style: interface 키워드를 type 키워드로 변경
  • Loading branch information
bbearcookie authored Dec 26, 2023
1 parent f3b2f62 commit 1b47e13
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 123 deletions.
12 changes: 8 additions & 4 deletions app/quizzes/[id]/choice-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import Button from '@/components/common/buttons/button';
import LoadingSpinner from '@/components/common/loading-spinner/loading-spinner';
import MarkDown from '@/components/ui/markdown';

export default function ChoiceForm({ quizId }: { quizId: number }) {
type ChoiceFormProps = {
quizId: number;
children?: React.ReactNode;
};

export default function ChoiceForm({ quizId, children }: ChoiceFormProps) {
const [errorMessage, setErrorMessage] = useState('');
const router = useRouter();

Expand Down Expand Up @@ -54,12 +59,11 @@ export default function ChoiceForm({ quizId }: { quizId: number }) {
/>
<h2 className="grow font-bold">{idx + 1}번 선택지</h2>
</label>
<MarkDown wrapLine={true} wrapLongLines={true}>
{choice.description}
</MarkDown>
<MarkDown wrapLongLines={true}>{choice.description}</MarkDown>
</div>
))}
{errorMessage && <p className="my-2 text-red-500">{errorMessage}</p>}
{children}
<Button
className="mt-4 flex h-10 w-full items-center justify-center disabled:bg-blue-primary"
disabled={isPending || isSuccess}
Expand Down
4 changes: 2 additions & 2 deletions app/quizzes/[id]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import BackHeader from '@/components/common/headers/back-header';
import NavLink from '@/components/common/link/nav-link';
import React from 'react';

interface LayoutProps {
type LayoutProps = {
children: React.ReactNode;
params: {
id: string;
};
}
};

export default function Layout({ children, params }: LayoutProps) {
const quizId = Number(params.id) ?? 0;
Expand Down
27 changes: 25 additions & 2 deletions app/quizzes/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,21 @@ import MarkDown from '@/components/ui/markdown';
import Link from 'next/link';
import ChoiceForm from './choice-form';
import quizOptions from '@/services/quiz/options';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@/components/ui/accordion';
import { Badge } from '@/components/ui/badge';

export default async function Page({ params }: { params: { id: string } }) {
const queryClient = new QueryClient();
const quizId = Number(params.id) ?? 0;

const [quiz] = await Promise.all([
const [quiz, hints] = await Promise.all([
queryClient.fetchQuery(quizOptions.detail(quizId)),
queryClient.fetchQuery(quizOptions.hints(quizId)),
queryClient.fetchQuery(quizOptions.choices(quizId)),
]);

Expand All @@ -35,7 +43,22 @@ export default async function Page({ params }: { params: { id: string } }) {
</div>
</section>
<MarkDown style={dracula}>{quiz?.description ?? ''}</MarkDown>
<ChoiceForm quizId={quizId} />
<ChoiceForm quizId={quizId}>
{hints && hints?.length > 0 ? (
<Accordion type="multiple">
<AccordionItem value="item-1">
<AccordionTrigger>힌트 보기</AccordionTrigger>
<AccordionContent className="flex flex-wrap gap-1">
{hints?.map((hint) => (
<Badge key={hint.id} variant="secondary">
{hint.description}
</Badge>
))}
</AccordionContent>
</AccordionItem>
</Accordion>
) : null}
</ChoiceForm>
</HydrationBoundary>
);
}
36 changes: 36 additions & 0 deletions components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';

import { cn } from '@/libs/utils';

const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: 'default',
},
}
);

export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}

export { Badge, badgeVariants };
4 changes: 2 additions & 2 deletions components/ui/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { dracula } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import remarkGfm from 'remark-gfm';

interface MarkdownProps extends React.ComponentProps<typeof SyntaxHighlighter> {
type MarkdownProps = React.ComponentProps<typeof SyntaxHighlighter> & {
className?: string;
}
};

export default function MarkDown({
children,
Expand Down
139 changes: 26 additions & 113 deletions libs/database.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,32 @@ export interface Database {
},
];
};
hints: {
Row: {
description: string;
id: number;
quiz_id: number;
};
Insert: {
description: string;
id?: number;
quiz_id: number;
};
Update: {
description?: string;
id?: number;
quiz_id?: number;
};
Relationships: [
{
foreignKeyName: 'hints_quiz_id_fkey';
columns: ['quiz_id'];
isOneToOne: false;
referencedRelation: 'quizzes';
referencedColumns: ['id'];
},
];
};
notes: {
Row: {
id: number;
Expand Down Expand Up @@ -189,119 +215,6 @@ export interface Database {
},
];
};
quizzes_before: {
Row: {
answer: number | null;
answer_description: string | null;
category: string | null;
choices: string | null;
created_at: string;
description: string | null;
id: number;
user_id: string | null;
};
Insert: {
answer?: number | null;
answer_description?: string | null;
category?: string | null;
choices?: string | null;
created_at?: string;
description?: string | null;
id?: number;
user_id?: string | null;
};
Update: {
answer?: number | null;
answer_description?: string | null;
category?: string | null;
choices?: string | null;
created_at?: string;
description?: string | null;
id?: number;
user_id?: string | null;
};
Relationships: [];
};
test_quizzes: {
Row: {
created_at: string;
description: string;
difficulty: Database['public']['Enums']['difficulty'];
id: number;
summary: string;
title: string;
updated_at: string;
user_id: string;
};
Insert: {
created_at?: string;
description: string;
difficulty: Database['public']['Enums']['difficulty'];
id?: number;
summary: string;
title: string;
updated_at?: string;
user_id: string;
};
Update: {
created_at?: string;
description?: string;
difficulty?: Database['public']['Enums']['difficulty'];
id?: number;
summary?: string;
title?: string;
updated_at?: string;
user_id?: string;
};
Relationships: [
{
foreignKeyName: 'test_quizzes_user_id_fkey';
columns: ['user_id'];
isOneToOne: false;
referencedRelation: 'users';
referencedColumns: ['id'];
},
];
};
test_quizzessubmissions: {
Row: {
created_at: string;
quiz_id: number;
success: boolean;
updated_at: string;
user_id: string;
};
Insert: {
created_at?: string;
quiz_id: number;
success: boolean;
updated_at?: string;
user_id: string;
};
Update: {
created_at?: string;
quiz_id?: number;
success?: boolean;
updated_at?: string;
user_id?: string;
};
Relationships: [
{
foreignKeyName: 'test_quizzessubmissions_quiz_id_fkey';
columns: ['quiz_id'];
isOneToOne: false;
referencedRelation: 'test_quizzes';
referencedColumns: ['id'];
},
{
foreignKeyName: 'test_quizzessubmissions_user_id_fkey';
columns: ['user_id'];
isOneToOne: false;
referencedRelation: 'users';
referencedColumns: ['id'];
},
];
};
users: {
Row: {
id: string;
Expand Down
11 changes: 11 additions & 0 deletions services/quiz/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ const quizAPI = {
return data;
},

getHintsOfQuiz: async (quizId: number) => {
const supabase: SupabaseClient<Database> = createClient();

const { data } = await supabase
.from('hints')
.select('*')
.eq('quiz_id', quizId);

return data;
},

postQuizSubmission: async (params: { quizId: number; choiceId: number }) => {
const { quizId, choiceId } = params;

Expand Down
6 changes: 6 additions & 0 deletions services/quiz/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ const quizOptions = {
queryKey: [...quizOptions.detail(quizId).queryKey, 'choices'],
queryFn: () => quizAPI.getChoicesOfQuiz(quizId),
}),

hints: (quizId: number) =>
queryOptions({
queryKey: [...quizOptions.detail(quizId).queryKey, 'hints'],
queryFn: () => quizAPI.getHintsOfQuiz(quizId),
}),
};

export default quizOptions;

0 comments on commit 1b47e13

Please sign in to comment.