Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(fe): use tanstack query for MySubmission #2250

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { submissionQueries } from '@/app/(client)/_libs/queries/submission'
import {
Dialog,
DialogTrigger,
Expand All @@ -10,94 +11,89 @@ import {
TooltipProvider,
TooltipTrigger
} from '@/components/shadcn/tooltip'
import { fetcherWithAuth } from '@/libs/utils'
import seeSubmissionIcon from '@/public/icons/see-submission.svg'
import type { SubmissionDetail, Submission, ContestProblem } from '@/types/type'
import type { ContestProblem } from '@/types/type'
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
import { ErrorBoundary } from '@suspensive/react'
import { useQuery } from '@tanstack/react-query'
import Image from 'next/image'
import { useParams } from 'next/navigation'
import { useEffect, useState } from 'react'
import { useState, Suspense } from 'react'
import SubmissionDetailContent from './SubmissionDetailContent'

interface SubmissionsResponse {
data: Submission[]
total: number
}

export default function MySubmission({ problem }: { problem: ContestProblem }) {
function MySubmissionContent({ problem }: { problem: ContestProblem }) {
const [isTooltipOpen, setIsTooltipOpen] = useState(false)
const [submission, setSubmission] = useState<SubmissionDetail | null>(null)
const [submissionId, setSubmissionId] = useState<number | null>(null)
const { contestId } = useParams()
const { contestId: contestIdString } = useParams()
const contestId = Number(contestIdString)

useEffect(() => {
const getSubmission = async () => {
const submissions: SubmissionsResponse = await fetcherWithAuth
.get(`contest/${contestId}/submission`, {
searchParams: {
take: 1,
problemId: problem.id
}
})
.json()
const firstSubmission = submissions.data[0]
setSubmissionId(firstSubmission.id)
const { data: latestSubmissionData, isLoading: isLoadingLatest } = useQuery(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useSuspenseQuery를 사용하면 isLoading을 사용하지 않아도 돼요!

submissionQueries.list({
contestId,
problemId: problem.id,
take: 1
})
)

const submission: SubmissionDetail = await fetcherWithAuth
.get(
`submission/${firstSubmission.id}?problemId=${problem.id}&contestId=${contestId}`
)
.json()
setSubmission(submission)
}
getSubmission()
}, [contestId, problem.id])
const latestSubmission = latestSubmissionData?.data?.[0]
const latestSubmissionId = latestSubmission?.id ?? 0

if (!submission || !submissionId) {
if (isLoadingLatest) {
return <Skeleton className="size-[25px]" />
}

if (!latestSubmissionId) {
return null
}

return (
<Dialog onOpenChange={() => setIsTooltipOpen(false)}>
<TooltipProvider>
<Tooltip>
<DialogTrigger asChild>
<TooltipTrigger asChild>
<Image
src={seeSubmissionIcon}
width={20}
height={20}
alt={'See submission'}
onClick={(e) => {
e.stopPropagation()
setIsTooltipOpen(true)
}}
onMouseEnter={() => setIsTooltipOpen(true)}
onMouseLeave={() => setIsTooltipOpen(false)}
/>
</TooltipTrigger>
</DialogTrigger>
{isTooltipOpen && (
<TooltipContent className="mr-4 bg-white">
<p className="text-xs text-neutral-900">
Click to check your latest submission.
</p>
<TooltipPrimitive.Arrow className="fill-white" />
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
<div onClick={(e) => e.stopPropagation()}>
<DialogContent className="max-h-[620px] max-w-[800px] justify-center">
<SubmissionDetailContent
contestId={contestId}
submissionId={latestSubmissionId}
problem={problem}
/>
</DialogContent>
</div>
</Dialog>
)
}

export default function MySubmission({ problem }: { problem: ContestProblem }) {
return (
<>
<Dialog onOpenChange={() => setIsTooltipOpen(false)}>
<TooltipProvider>
<Tooltip>
<DialogTrigger asChild>
<TooltipTrigger asChild>
<Image
src={seeSubmissionIcon}
width={20}
height={20}
alt={'See submission'}
onClick={(e) => {
e.stopPropagation()
setIsTooltipOpen(true)
}}
onMouseEnter={() => setIsTooltipOpen(true)}
onMouseLeave={() => setIsTooltipOpen(false)}
/>
</TooltipTrigger>
</DialogTrigger>
{isTooltipOpen && (
<TooltipContent className="mr-4 bg-white">
<p className="text-xs text-neutral-900">
Click to check your latest submission.
</p>
<TooltipPrimitive.Arrow className="fill-white" />
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
<div onClick={(e) => e.stopPropagation()}>
<DialogContent className="max-h-[620px] max-w-[800px] justify-center">
<SubmissionDetailContent
submission={submission}
submissionId={submissionId}
problem={problem}
/>
</DialogContent>
</div>
</Dialog>
</>
<ErrorBoundary fallback={null}>
<Suspense fallback={<Skeleton className="size-[25px]" />}>
eunnbi marked this conversation as resolved.
Show resolved Hide resolved
<MySubmissionContent problem={problem} />
</Suspense>
</ErrorBoundary>
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { submissionQueries } from '@/app/(client)/_libs/queries/submission'
import CodeEditor from '@/components/CodeEditor'
import { ScrollArea, ScrollBar } from '@/components/shadcn/scroll-area'
import {
Expand All @@ -11,17 +12,30 @@ import {
TableRow
} from '@/components/shadcn/table'
import { dateFormatter, getResultColor } from '@/libs/utils'
import type { ContestProblem, Language, SubmissionDetail } from '@/types/type'
import type { ContestProblem, Language } from '@/types/type'
import { ErrorBoundary } from '@suspensive/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { Suspense } from 'react'

export default function SubmissionDetailContent({
submissionId,
submission,
problem
}: {
interface SubmissionDetailProps {
contestId: number
submissionId: number
submission: SubmissionDetail
problem: ContestProblem
}) {
}

function SubmissionDetail({
contestId,
submissionId,
problem
}: SubmissionDetailProps) {
const { data: submission } = useSuspenseQuery(
submissionQueries.detail({
contestId,
submissionId,
problemId: problem.id
})
)

return (
<ScrollArea className="mt-5 max-h-[540px] w-[760px]">
<div className="ml-20 flex w-[612px] flex-col gap-4">
Expand Down Expand Up @@ -121,3 +135,21 @@ export default function SubmissionDetailContent({
</ScrollArea>
)
}

export default function SubmissionDetailContetnt({
contestId,
submissionId,
problem
}: SubmissionDetailProps) {
return (
<ErrorBoundary fallback={null}>
<Suspense fallback={null}>
Comment on lines +145 to +146
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error boundary를 fallback으로 FetchErrorFallback 공통 컴포넌트 사용하면 좋을 것 같아요!
그리고 간단하게 loading ui도 만들면 좋을 것 같아요!

<SubmissionDetail
contestId={contestId}
submissionId={submissionId}
problem={problem}
/>
</Suspense>
</ErrorBoundary>
)
}
47 changes: 47 additions & 0 deletions apps/frontend/app/(client)/_libs/apis/submission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { safeFetcherWithAuth } from '@/libs/utils'
import type { Submission, SubmissionDetail } from '@/types/type'
import type { PaginationQueryParams } from './types'

export interface GetSubmissionListRequest extends PaginationQueryParams {
contestId: number
problemId: number
}

export interface GetSubmissionListResponse {
data: Submission[]
total: number
}

export const getSubmissionList = async ({
contestId,
problemId,
...searchParams
}: GetSubmissionListRequest): Promise<GetSubmissionListResponse> => {
const response = await safeFetcherWithAuth.get(
`contest/${contestId}/submission`,
{
searchParams: { ...searchParams, problemId }
}
)
const data = await response.json<GetSubmissionListResponse>()
return data
}

export interface GetSubmissionDetailRequest {
contestId: number
problemId: number
submissionId: number
}

export const getSubmissionDetail = async ({
contestId,
problemId,
submissionId
}: GetSubmissionDetailRequest): Promise<SubmissionDetail> => {
const response = await safeFetcherWithAuth.get(`submission/${submissionId}`, {
searchParams: { problemId, contestId }
})

const data = await response.json<SubmissionDetail>()
return data
}
39 changes: 39 additions & 0 deletions apps/frontend/app/(client)/_libs/queries/submission.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { queryOptions } from '@tanstack/react-query'
import {
getSubmissionList,
getSubmissionDetail,
type GetSubmissionListRequest,
type GetSubmissionDetailRequest
} from '../apis/submission'

export const submissionQueries = {
all: ({ contestId, problemId }: { contestId: number; problemId: number }) =>
['submission', 'contest', contestId, { problemId }] as const,

lists: ({ contestId, problemId }: { contestId: number; problemId: number }) =>
[...submissionQueries.all({ contestId, problemId }), 'list'] as const,

list: ({ contestId, problemId, ...searchParams }: GetSubmissionListRequest) =>
queryOptions({
queryKey: [
...submissionQueries.lists({ contestId, problemId }),
{ ...searchParams }
] as const,
queryFn: () =>
getSubmissionList({ contestId, problemId, ...searchParams })
}),

detail: ({
contestId,
submissionId,
problemId
}: GetSubmissionDetailRequest) =>
queryOptions({
queryKey: [
...submissionQueries.all({ contestId, problemId }),
'detail',
submissionId
] as const,
queryFn: () => getSubmissionDetail({ contestId, submissionId, problemId })
})
}