diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 64e2ea9b81..b85bbfa094 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -15,6 +15,7 @@ "customizations": { "vscode": { "extensions": [ + "apollographql.vscode-apollo", "bradlc.vscode-tailwindcss", "bruno-api-client.bruno", "csstools.postcss", diff --git a/.github/actions/setup-pnpm/action.yml b/.github/actions/setup-pnpm/action.yml index 016db93faa..2c23ed2758 100644 --- a/.github/actions/setup-pnpm/action.yml +++ b/.github/actions/setup-pnpm/action.yml @@ -10,7 +10,7 @@ inputs: runs: using: 'composite' steps: - - uses: pnpm/action-setup@v2 + - uses: pnpm/action-setup@v3 with: version: latest - uses: actions/setup-node@v4 diff --git a/.gitpod.yml b/.gitpod.yml index 9a7771856b..8eda1381be 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -37,6 +37,7 @@ github: vscode: extensions: + - apollographql.vscode-apollo - bradlc.vscode-tailwindcss - bruno-api-client.bruno - csstools.postcss diff --git a/.prettierignore b/.prettierignore index f72ea2e35e..3aba1a4e6f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ node_modules/ pnpm-lock.yaml .pnpm-store/ @generated/ +__generated__/ schema.gql collection/ .next/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 082bc94c02..e93441c845 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,11 @@ { - "recommendations": ["ms-vscode-remote.remote-containers"] + // Recommendations for Frontend team, developing without devcontainer + "recommendations": [ + "apollographql.vscode-apollo", + "dbaeumer.vscode-eslint", + "donjayamanne.githistory", + "eamodio.gitlens", + "EditorConfig.EditorConfig", + "esbenp.prettier-vscode" + ] } diff --git a/apollo.config.js b/apollo.config.js new file mode 100644 index 0000000000..6b67e7fb18 --- /dev/null +++ b/apollo.config.js @@ -0,0 +1,10 @@ +module.exports = { + client: { + includes: ['./frontend-client/**/*.ts', './frontend-client/**/*.tsx'], + excludes: ['**/__generated__/**'], + service: { + name: 'codedang-graphql-app', + url: 'https://dev.codedang.com/graphql' + } + } +} diff --git a/frontend-client/.eslintrc.js b/frontend-client/.eslintrc.js index 3ec55e95bc..ca3136496b 100644 --- a/frontend-client/.eslintrc.js +++ b/frontend-client/.eslintrc.js @@ -22,7 +22,23 @@ module.exports = { namedComponents: 'function-declaration' } ], - 'func-style': ['off'] + 'func-style': ['off'], + 'no-restricted-imports': [ + 'error', + { + name: '@apollo/client', + importNames: ['gql'], + message: 'Please use @generated instead.' + }, + { + name: '@/__generated__', + message: 'Please use @generated instead.' + }, + { + name: '@/__generated__/graphql', + message: 'Please use @generated/graphql instead.' + } + ] } } ] diff --git a/frontend-client/.gitignore b/frontend-client/.gitignore index fd3dbb571a..16e26f00cf 100644 --- a/frontend-client/.gitignore +++ b/frontend-client/.gitignore @@ -34,3 +34,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# GraphQL Codegen +__generated__/ diff --git a/frontend-client/Dockerfile b/frontend-client/Dockerfile deleted file mode 100644 index 5733b0c351..0000000000 --- a/frontend-client/Dockerfile +++ /dev/null @@ -1,62 +0,0 @@ -# [NOTE] Build image from the root directory of this repository. -# ex) `docker build -f frontend-client/Dockerfile .` - -FROM node:20-alpine AS base - -# Install dependencies only when needed -FROM base AS deps -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat -WORKDIR /app - -# Install dependencies -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ -COPY frontend-client ./frontend-client -RUN corepack enable && pnpm --filter=frontend-client deploy out - - -# Rebuild the source code only when needed -FROM base AS builder - -WORKDIR /app -COPY --from=deps /app/out . - -# Next.js collects completely anonymous telemetry data about general usage. -# Learn more here: https://nextjs.org/telemetry -# Uncomment the following line in case you want to disable telemetry during the build. -# ENV NEXT_TELEMETRY_DISABLED 1 - -RUN npm run build - - -# Production image, copy all the files and run next -FROM base AS runner -WORKDIR /app - -ENV NODE_ENV production -# Uncomment the following line in case you want to disable telemetry during runtime. -# ENV NEXT_TELEMETRY_DISABLED 1 - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder /app/public ./public - -# Set the correct permission for prerender cache -RUN mkdir .next -RUN chown nextjs:nodejs .next - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static - -USER nextjs - -EXPOSE 5525 - -ENV PORT 5525 -# set hostname to localhost -ENV HOSTNAME "0.0.0.0" - -CMD ["node", "server.js"] diff --git a/frontend-client/Dockerfile.dockerignore b/frontend-client/Dockerfile.dockerignore deleted file mode 100644 index c18d4657c7..0000000000 --- a/frontend-client/Dockerfile.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -Dockerfile -Dockerfile.dockerignore -node_modules -npm-debug.log -README.md -.next -.git diff --git a/frontend-client/app/admin/_components/ApolloProvider.tsx b/frontend-client/app/admin/_components/ApolloProvider.tsx new file mode 100644 index 0000000000..86122a32d2 --- /dev/null +++ b/frontend-client/app/admin/_components/ApolloProvider.tsx @@ -0,0 +1,44 @@ +'use client' + +import { auth } from '@/lib/auth' +import { adminBaseUrl } from '@/lib/vars' +import { + ApolloClient, + ApolloLink, + ApolloProvider, + InMemoryCache, + createHttpLink +} from '@apollo/client' +import { setContext } from '@apollo/client/link/context' + +interface Props { + children: React.ReactNode +} + +export default function ClientApolloProvider({ children }: Props) { + const httpLink = createHttpLink({ + uri: adminBaseUrl + }) + const authLink = setContext(async (_, { headers }) => { + const session = await auth() + return { + headers: { + ...headers, + authorization: session?.token.accessToken + } + } + }) + const link = ApolloLink.from([authLink.concat(httpLink)]) + + const client = new ApolloClient({ + cache: new InMemoryCache(), + link, + defaultContext: { + fetchOptions: { + next: { revalidate: 0 } + } + } + }) + + return {children} +} diff --git a/frontend-client/app/admin/layout.tsx b/frontend-client/app/admin/layout.tsx index 380a6bc47b..b7ee5df38b 100644 --- a/frontend-client/app/admin/layout.tsx +++ b/frontend-client/app/admin/layout.tsx @@ -3,33 +3,36 @@ import { Separator } from '@/components/ui/separator' import type { Route } from 'next' import Link from 'next/link' import { FaArrowRightFromBracket } from 'react-icons/fa6' +import ClientApolloProvider from './_components/ApolloProvider' // import GroupSelect from './_components/GroupSelect' import SideBar from './_components/SideBar' export default function Layout({ children }: { children: React.ReactNode }) { return ( -
- - - {children} -
+ +
+ + + {children} +
+
) } diff --git a/frontend-client/app/admin/problem/[id]/page.tsx b/frontend-client/app/admin/problem/[id]/page.tsx index bfcef4cde0..549d556c17 100644 --- a/frontend-client/app/admin/problem/[id]/page.tsx +++ b/frontend-client/app/admin/problem/[id]/page.tsx @@ -1,5 +1,6 @@ 'use client' +import { gql } from '@generated' import CheckboxSelect from '@/components/CheckboxSelect' import OptionSelect from '@/components/OptionSelect' import TagsSelect from '@/components/TagsSelect' @@ -15,19 +16,13 @@ import { import { ScrollArea } from '@/components/ui/scroll-area' import { Switch } from '@/components/ui/switch' import { Textarea } from '@/components/ui/textarea' -import { fetcherGql, cn } from '@/lib/utils' -import type { - Level, - Language, - Testcase, - Sample, - Template, - Tag -} from '@/types/type' -import { gql } from '@apollo/client' +import { cn } from '@/lib/utils' +import type { Language, Sample } from '@/types/type' +import { useMutation, useQuery } from '@apollo/client' +import type { UpdateProblemInput } from '@generated/graphql' import { zodResolver } from '@hookform/resolvers/zod' import Link from 'next/link' -import { useEffect, useState } from 'react' +import { useState } from 'react' import { useForm, Controller } from 'react-hook-form' import { FaEye, FaEyeSlash } from 'react-icons/fa' import { FaAngleLeft } from 'react-icons/fa6' @@ -40,49 +35,15 @@ import { z } from 'zod' import ExampleTextarea from '../_components/ExampleTextarea' import Label from '../_components/Lable' import type { TemplateLanguage } from '../utils' -import { GET_TAGS, inputStyle, languageOptions, levels } from '../utils' - -interface UpdateProblemInput { - id: number - title: string - visible: boolean - difficulty: Level - languages: Language[] - tags: { - create: number[] - delete: number[] - } - description: string - inputDescription: string - outputDescription: string - samples: Sample[] - testcases: Testcase[] - timeLimit: number - memoryLimit: number - hint?: string - source?: string - template?: Template[] -} - -interface GetProblem { - title: string - // visible: boolean - difficulty: Level - languages: Language[] - problemTag: { tag: Tag }[] - description: string - inputDescription: string - outputDescription: string - problemTestcase: Testcase[] - // problemSample: Sample[] - timeLimit: number - memoryLimit: number - hint: string - source: string - template: string[] -} +import { + GET_TAGS, + inputStyle, + languageMapper, + languageOptions, + levels +} from '../utils' -const GET_PROBLEM = gql` +const GET_PROBLEM = gql(` query GetProblem($groupId: Int!, $id: Int!) { getProblem(groupId: $groupId, id: $id) { title @@ -108,21 +69,23 @@ const GET_PROBLEM = gql` template } } -` +`) -const UPDATE_PROBLEM = gql` +const UPDATE_PROBLEM = gql(` mutation UpdateProblem($groupId: Int!, $input: UpdateProblemInput!) { updateProblem(groupId: $groupId, input: $input) { id createdById groupId title - visible + isVisible difficulty languages - tags { - create - delete + problemTag { + tag { + id + name + } } description inputDescription @@ -130,9 +93,8 @@ const UPDATE_PROBLEM = gql` samples { input output - scoreWeight } - testcases { + problemTestcase { input output scoreWeight @@ -141,17 +103,10 @@ const UPDATE_PROBLEM = gql` memoryLimit hint source - template { - code { - id - locked - text - } - language - } + template } } -` +`) const schema = z.object({ title: z.string().min(1).max(25), @@ -212,103 +167,39 @@ const schema = z.object({ export default function Page({ params }: { params: { id: string } }) { const { id } = params - const [showHint, setShowHint] = useState(true) - const [showSource, setShowSource] = useState(true) + const [showHint, setShowHint] = useState(true) + const [showSource, setShowSource] = useState(true) const [samples, setSamples] = useState([{ input: '', output: '' }]) - const [testcases, setTestcases] = useState([ - { input: '', output: '' } - ]) - const [tags, setTags] = useState([]) const [languages, setLanguages] = useState([]) - const [problemData, setProblemData] = useState() - const [fetchedTags, setFetchedTags] = useState([]) - const [fetchedLangauges, setFetchedLanguages] = useState([]) - const [fetchedDifficulty, setFetchedDifficulty] = useState() - const [fetchedDescription, setFetchedDescription] = useState('') - const [fetchedTemplateLanguage, setFetchedTemplateLanguage] = useState< - Language[] - >([]) - - useEffect(() => { - fetcherGql(GET_TAGS).then((data) => { - const transformedData = data.getTags.map( - (tag: { id: string; name: string }) => ({ - ...tag, - id: Number(tag.id) - }) - ) - setTags(transformedData) - }) + const { data: tagsData } = useQuery(GET_TAGS) + const tags = + tagsData?.getTags.map(({ id, name }) => ({ id: +id, name })) ?? [] - fetcherGql(GET_PROBLEM, { + const { data: problemData } = useQuery(GET_PROBLEM, { + variables: { groupId: 1, - id: Number(id) - }).then((data) => { - setProblemData(data.getProblem) - setFetchedDifficulty(data.getProblem.difficulty) - setFetchedLanguages(data.getProblem.languages) - setFetchedTags( - data.getProblem.problemTag.map((problemTag: { tag: Tag }) => - Number(problemTag.tag.id) - ) - ) - setFetchedDescription(data.getProblem.description) - setFetchedTemplateLanguage( - data.getProblem.template.map((template: string) => { - const parsedTemplate = JSON.parse(template)[0] - return parsedTemplate.language - }) - ) - setLanguages( - data.getProblem.languages.map((language: Language) => ({ - language, - isVisible: fetchedTemplateLanguage.includes(language) ? true : false - })) - ) - }) - }, [id, problemData]) - - useEffect(() => { - if (problemData) { - // TODO: add visible and samples - setValue('title', problemData.title) - setValue('difficulty', problemData.difficulty) - setValue('languages', problemData.languages) - setValue( - 'tags.create', - problemData.problemTag.map((problemTag) => Number(problemTag.tag.id)) - ) - setValue( - 'tags.delete', - problemData.problemTag.map((problemTag) => Number(problemTag.tag.id)) - ) - setValue('description', problemData.description) - setValue('inputDescription', problemData.inputDescription) - setValue('outputDescription', problemData.outputDescription) - setValue('testcases', problemData.problemTestcase) - setValue('timeLimit', problemData.timeLimit) - setValue('memoryLimit', problemData.memoryLimit) - setValue('hint', problemData.hint) - setValue('source', problemData.source) - setValue( - 'template', - problemData.template.map((template: string) => { - const parsedTemplate = JSON.parse(template)[0] - return { - language: parsedTemplate.language, - code: [ - { - id: parsedTemplate.code[0].id, - text: parsedTemplate.code[0].text, - locked: parsedTemplate.code[0].locked - } - ] - } - }) - ) + id: +id } - }, [problemData]) + }) + + const fetchedDescription = problemData?.getProblem.description + const fetchedDifficulty = problemData?.getProblem.difficulty + const fetchedLangauges = problemData?.getProblem.languages ?? [] + const fetchedTags = + problemData?.getProblem.problemTag.map(({ tag }) => +tag.id) ?? [] + + const fetchedTemplateLanguage = + problemData?.getProblem.template?.map( + (template: string) => JSON.parse(template)[0].language + ) ?? [] + + setLanguages( + problemData?.getProblem.languages?.map((language: Language) => ({ + language, + isVisible: fetchedTemplateLanguage.includes(language) ? true : false + })) ?? [] + ) const { handleSubmit, @@ -320,42 +211,95 @@ export default function Page({ params }: { params: { id: string } }) { } = useForm({ resolver: zodResolver(schema), defaultValues: { - samples: [{ input: '', output: '' }], + samples: {}, template: [] } }) - // TODO: Create Problem 에 sample, visible 추가 시 변경 - const onSubmit = async (data: UpdateProblemInput) => { - try { - const res = await fetcherGql(UPDATE_PROBLEM, { - groupId: 1, - input: data + if (problemData) { + const data = problemData.getProblem + + setValue('title', data.title) + setValue('difficulty', data.difficulty) + setValue('languages', data.languages ?? []) + setValue( + 'tags.create', + data.problemTag.map((problemTag) => Number(problemTag.tag.id)) + ) + setValue( + 'tags.delete', + data.problemTag.map((problemTag) => Number(problemTag.tag.id)) + ) + setValue('description', data.description) + setValue('inputDescription', data.inputDescription) + setValue('outputDescription', data.outputDescription) + setValue('testcases', data.problemTestcase) + setValue('timeLimit', data.timeLimit) + setValue('memoryLimit', data.memoryLimit) + setValue('hint', data.hint) + setValue('source', data.source) + setValue( + 'template', + data.template?.map((template: string) => { + const parsedTemplate = JSON.parse(template)[0] + return { + language: parsedTemplate.language, + code: [ + { + id: parsedTemplate.code[0].id, + text: parsedTemplate.code[0].text, + locked: parsedTemplate.code[0].locked + } + ] + } }) - console.log(res) - } catch (error) { - console.error(error) - console.log(data) + ) + } + + const [updateProblem, { error }] = useMutation(UPDATE_PROBLEM) + const onSubmit = async (input: UpdateProblemInput) => { + await updateProblem({ + variables: { + groupId: 1, + input + } + }) + if (error) { + toast.error('Failed to update problem') } } - const addExample = (type: 'samples' | 'testcases') => { - const currentValues = getValues(type) - setValue(type, [...currentValues, { input: '', output: '' }]) - type === 'samples' - ? setSamples(() => [...samples, { input: '', output: '' }]) - : setTestcases(() => [...testcases, { input: '', output: '' }]) + const addSample = () => { + const values = getValues('samples.create') + const newSample = { input: '', output: '' } + setValue('samples.create', [...values, newSample]) + setSamples((prev) => [...prev, newSample]) + } + + const addTestcase = () => { + const values = getValues('testcases') ?? [] + const newTestcase = { input: '', output: '' } + setValue('testcases', [...values, newTestcase]) + } + + const removeSample = (index: number) => { + if (samples.length <= 1) { + toast.warning('At least one sample is required') + return + } + const samplesToDelete = getValues('samples.delete') + setValue('samples.delete', [...samplesToDelete, index]) + setSamples(samples.filter((_, i) => i !== index)) } - const removeExample = (type: 'samples' | 'testcases', index: number) => { - const currentValues = getValues(type) - if (currentValues.length === 1) { - toast.warning(`At least one ${type} is required`) + const removeTestcase = (index: number) => { + const values = getValues('testcases') ?? [] + if (values.length <= 1) { + toast.warning('At least one testcase is required') return } - const updatedValues = currentValues.filter((_, i) => i !== index) - setValue(type, updatedValues) - type === 'samples' ? setSamples(updatedValues) : setTestcases(updatedValues) + const updatedValues = values.filter((_, i) => i !== index) + setValue('testcases', updatedValues) } return ( @@ -385,7 +329,7 @@ export default function Page({ params }: { params: { id: string } }) { {errors.title && (
- {getValues('title').length === 0 + {getValues('title')?.length === 0 ? 'required' : errors.title.message}
@@ -412,7 +356,7 @@ export default function Page({ params }: { params: { id: string } }) {
(
- {errors.visible && ( + {errors.isVisible && (
required @@ -597,7 +541,7 @@ export default function Page({ params }: { params: { id: string } }) {
addExample('samples')} + onClick={addSample} className="h-[18px] w-[45px] cursor-pointer items-center justify-center bg-gray-200/60 p-0 text-xs font-medium text-gray-500 shadow-sm hover:bg-gray-200" > + add @@ -605,15 +549,15 @@ export default function Page({ params }: { params: { id: string } }) {
{getValues('samples') && - getValues('samples').map((_sample, index) => ( + samples.map((_, index) => (
removeExample('samples', index)} + onRemove={() => removeSample(index)} inputName={`samples.${index}.input`} outputName={`samples.${index}.output`} register={register} /> - {errors.samples?.[index] && ( + {errors.samples && samples[index] && (
required @@ -628,7 +572,7 @@ export default function Page({ params }: { params: { id: string } }) {
addExample('testcases')} + onClick={addTestcase} className="h-[18px] w-[45px] cursor-pointer items-center justify-center bg-gray-200/60 p-0 text-xs font-medium text-gray-500 shadow-sm hover:bg-gray-200" > + add @@ -636,11 +580,11 @@ export default function Page({ params }: { params: { id: string } }) {
{getValues('testcases') && - getValues('testcases').map((_testcase, index) => ( + getValues('testcases')?.map((_, index) => (
removeExample('testcases', index)} + onRemove={() => removeTestcase(index)} inputName={`testcases.${index}.input`} outputName={`testcases.${index}.output`} register={register} @@ -778,7 +722,8 @@ export default function Page({ params }: { params: { id: string } }) { ) ) setValue(`template.${index}`, { - language: templateLanguage.language, + language: + languageMapper[templateLanguage.language], code: [ { id: index, diff --git a/frontend-client/app/admin/problem/create/page.tsx b/frontend-client/app/admin/problem/create/page.tsx index 944f48fca9..bfa1aafd6e 100644 --- a/frontend-client/app/admin/problem/create/page.tsx +++ b/frontend-client/app/admin/problem/create/page.tsx @@ -1,5 +1,6 @@ 'use client' +import { gql } from '@generated' import CheckboxSelect from '@/components/CheckboxSelect' import OptionSelect from '@/components/OptionSelect' import TagsSelect from '@/components/TagsSelect' @@ -15,19 +16,13 @@ import { import { ScrollArea } from '@/components/ui/scroll-area' import { Switch } from '@/components/ui/switch' import { Textarea } from '@/components/ui/textarea' -import { fetcherGql, cn } from '@/lib/utils' -import type { - Level, - Language, - Testcase, - Template, - Tag, - Sample -} from '@/types/type' -import { gql } from '@apollo/client' +import { cn } from '@/lib/utils' +import type { Language, Testcase, Sample } from '@/types/type' +import { useMutation, useQuery } from '@apollo/client' +import { Level, type CreateProblemInput } from '@generated/graphql' import { zodResolver } from '@hookform/resolvers/zod' import Link from 'next/link' -import { useEffect, useState } from 'react' +import { useState } from 'react' import { useForm, Controller } from 'react-hook-form' import { FaEye, FaEyeSlash } from 'react-icons/fa' import { FaAngleLeft } from 'react-icons/fa6' @@ -39,47 +34,51 @@ import { toast } from 'sonner' import { z } from 'zod' import ExampleTextarea from '../_components/ExampleTextarea' import Label from '../_components/Lable' -import type { TemplateLanguage } from '../utils' -import { GET_TAGS, inputStyle, languageOptions, levels } from '../utils' +import { GET_TAGS, languageMapper } from '../utils' -interface CreateProblemInput { - title: string - visible: boolean - difficulty: Level - languages: Language[] - tagIds: number[] - description: string - inputDescription: string - outputDescription: string - samples: Sample[] - testcases: Testcase[] - timeLimit: number - memoryLimit: number - hint?: string - source?: string - template?: Template[] +const inputStyle = + 'border-gray-200 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-950' + +// dummy data +const levels = ['Level1', 'Level2', 'Level3', 'Level4', 'Level5'] +const languageOptions: Language[] = [ + 'C', + 'Cpp', + 'Golang', + 'Java', + 'Python2', + 'Python3' +] + +interface TemplateLanguage { + language: Language + showTemplate: boolean } -const CREATE_PROBLEM = gql` +const CREATE_PROBLEM = gql(` mutation CreateProblem($groupId: Int!, $input: CreateProblemInput!) { createProblem(groupId: $groupId, input: $input) { id createdById groupId title - visible + isVisible difficulty languages - tagIds + problemTag { + tag { + id + name + } + } description inputDescription outputDescription samples { input output - scoreWeight } - testcases { + problemTestcase { input output scoreWeight @@ -88,17 +87,10 @@ const CREATE_PROBLEM = gql` memoryLimit hint source - template { - code { - id - text - locked - } - language - } + template } } -` +`) const schema = z.object({ title: z.string().min(1).max(25), @@ -164,20 +156,11 @@ export default function Page() { const [testcases, setTestcases] = useState([ { input: '', output: '' } ]) - const [tags, setTags] = useState([]) const [languages, setLanguages] = useState([]) - useEffect(() => { - fetcherGql(GET_TAGS).then((data) => { - const transformedData = data.getTags.map( - (tag: { id: string; name: string }) => ({ - ...tag, - id: Number(tag.id) - }) - ) - setTags(transformedData) - }) - }, []) + const { data: tagsData } = useQuery(GET_TAGS) + const tags = + tagsData?.getTags.map(({ id, name }) => ({ id: +id, name })) ?? [] const { handleSubmit, @@ -189,7 +172,7 @@ export default function Page() { } = useForm({ resolver: zodResolver(schema), defaultValues: { - difficulty: 'Level1', + difficulty: Level.Level1, samples: [{ input: '', output: '' }], testcases: [{ input: '', output: '' }], hint: '', @@ -198,17 +181,17 @@ export default function Page() { } }) - // TODO: Create Problem 에 sample, visible 추가 시 변경 - const onSubmit = async (data: CreateProblemInput) => { - try { - const res = await fetcherGql(CREATE_PROBLEM, { + // TODO: createProblem에 sample, visible 추가 시 변경 + const [createProblem, { error }] = useMutation(CREATE_PROBLEM) + const onSubmit = async (input: CreateProblemInput) => { + await createProblem({ + variables: { groupId: 1, - input: data - }) - console.log(res) - } catch (error) { - console.error(error) - console.log(data) + input + } + }) + if (error) { + toast.error('Failed to create problem') } } @@ -285,7 +268,7 @@ export default function Page() {
(
- {errors.visible && ( + {errors.isVisible && (
required @@ -358,15 +341,15 @@ export default function Page() { setLanguages( selectedLanguages.map((language) => ({ language, - isVisible: + showTemplate: languages.filter( (prev) => prev.language === language ).length > 0 ? languages.filter( (prev) => prev.language === language - )[0].isVisible + )[0].showTemplate : false - })) as TemplateLanguage[] + })) ) }} /> @@ -615,101 +598,97 @@ export default function Page() {
{languages && - (languages as TemplateLanguage[]).map( - (templateLanguage, index) => ( -
-
-
- - { - setLanguages((prev) => - prev.map((prevLanguage) => - prevLanguage.language === - templateLanguage.language - ? { - ...prevLanguage, - isVisible: !prevLanguage.isVisible - } - : prevLanguage - ) + languages.map((templateLanguage, index) => ( +
+
+
+ + { + setLanguages((prev) => + prev.map((prevLanguage) => + prevLanguage.language === + templateLanguage.language + ? { + ...prevLanguage, + isVisible: !prevLanguage.showTemplate + } + : prevLanguage ) - setValue(`template.${index}`, { - language: templateLanguage.language, - code: [ - { - id: index, - text: '', - locked: true - } - ] - }) - }} - className="data-[state=checked]:bg-black data-[state=unchecked]:bg-gray-300" - /> -
- {templateLanguage.isVisible && ( -