diff --git a/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/CheckerPage.tsx b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/CheckerPage.tsx index 2d84d471c2..ad9123e2a8 100644 --- a/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/CheckerPage.tsx +++ b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/CheckerPage.tsx @@ -1,3 +1,15 @@ +import { FileUploadSection } from './FileUploadSection' + export function CheckerPage() { - return
This is Checker page
+ return ( + + ) } diff --git a/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/FileUploadSection.tsx b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/FileUploadSection.tsx new file mode 100644 index 0000000000..83b63d6e6a --- /dev/null +++ b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/FileUploadSection.tsx @@ -0,0 +1,169 @@ +import { Badge } from '@/components/shadcn/badge' +import { cn } from '@/libs/utils' +import fileIcon from '@/public/icons/file_gray.svg' +import InfoIcon from '@/public/icons/info-icon-gray.svg' +import trashcanIcon from '@/public/icons/trashcan2-gray.svg' +import uploadIcon from '@/public/icons/upload-blue.svg' +import Image from 'next/image' +import type { ChangeEvent, ReactNode } from 'react' +import { useState, useRef } from 'react' + +interface UploadedFile { + name: string + size: string +} + +interface FileUploadSectionProps { + title: string + description: string + emptyMessages: string[] + accept: string + optional?: boolean + children?: ReactNode + className?: string +} + +const FORMAT_EXAMPLES = [ + 'Generator : generator.cpp / generator.py', + 'Validator : validator.cpp (testlib 등)', + 'Checker : checker.cpp (특수 채점)' +] + +export function FileUploadSection({ + title, + description, + emptyMessages, + accept, + optional, + children, + className +}: FileUploadSectionProps) { + const [file, setFile] = useState(null) + const fileInputRef = useRef(null) + + const handleFileChange = (e: ChangeEvent) => { + const selectedFiles = e.target.files + if (!selectedFiles || selectedFiles.length === 0) { + return + } + + const allowedExtensions = accept.split(',').map((ext) => ext.trim()) + const selected = selectedFiles[0] + + if (!allowedExtensions.some((ext) => selected.name.endsWith(ext))) { + alert(`${accept} 파일만 업로드 가능합니다.`) + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + return + } + + setFile({ + name: selected.name, + size: `${(selected.size / 1024).toFixed(1)}KB` + }) + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + } + + const handleDelete = () => { + setFile(null) + } + + return ( +
+
+
+

+ {title} + {optional && ( + + 선택 + + )} +

+

+ {description} +

+
+ + +
+ +
+ {file === null ? ( +
+ info + {emptyMessages.map((msg, idx) => ( +

+ {msg} +

+ ))} +
+ ) : ( +
+
+ + file + +
+

{file.name}

+

+ {file.size} +

+
+
+ +
+ )} +
+ +

+ 포맷 예시 +

+
+ {FORMAT_EXAMPLES.map((example, index) => ( +
+ +

{example}

+
+ ))} +
+ + {children} +
+ ) +} diff --git a/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/GeneratorPage.tsx b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/GeneratorPage.tsx index 62f8c54ae2..fd4eb18f2f 100644 --- a/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/GeneratorPage.tsx +++ b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/GeneratorPage.tsx @@ -1,16 +1,15 @@ -import { useSuspenseQuery } from '@tanstack/react-query' +import { FileUploadSection } from './FileUploadSection' export function GeneratorPage() { - // 스켈레톤 확인을 위한 더미코드 - useSuspenseQuery({ - queryKey: ['SongJunGyu'], - queryFn: () => - new Promise((resolve) => { - setTimeout(() => { - resolve('') - }, 5000) - }) - }) - - return
This is Generator page
+ return ( + + ) } diff --git a/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/ProblemCreateContainer.tsx b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/ProblemCreateContainer.tsx index 1b7e89f4e2..cf7242642b 100644 --- a/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/ProblemCreateContainer.tsx +++ b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/ProblemCreateContainer.tsx @@ -12,17 +12,14 @@ import { useState } from 'react' import { AiFillFile } from 'react-icons/ai' import { BsPeopleFill } from 'react-icons/bs' import { FaBook, FaPen } from 'react-icons/fa' -import { FaSquareCheck } from 'react-icons/fa6' -import { PiMagnifyingGlassFill, PiWrenchFill } from 'react-icons/pi' -import { CheckerPage } from './CheckerPage' +import { PiWrenchFill } from 'react-icons/pi' import { CollaborationPage } from './CollaborationPage' -import { GeneratorPage } from './GeneratorPage' import { ProblemCreateContentSkeleton } from './ProblemCreateSkeletons' import { SolutionPage } from './SolutionPage' import { StatementPage } from './StatementPage' import { TestsPage } from './TestsPage' +import { ToolsPage } from './ToolsPage' import { UploadButton } from './UploadButton' -import { ValidatorPage } from './ValidatorPage' export function ProblemCreateContainer() { // 스켈레톤 확인을 위한 더미코드 @@ -62,24 +59,10 @@ export function ProblemCreateContainer() { }, { Icon: PiWrenchFill, - label: 'Generator', - text: '테스트 생성', - subText: '테스트 입력 생성', - Component: GeneratorPage - }, - { - Icon: PiMagnifyingGlassFill, - label: 'Validator', - text: '입력 검증', - subText: '입력 및 검증', - Component: ValidatorPage - }, - { - Icon: FaSquareCheck, - label: 'Checker', - text: '특수 채점', - subText: '특수 채점 기능', - Component: CheckerPage + label: 'Tools', + text: '문제 생성 도구', + subText: '생성 및 입력 검증, 특수 채점', + Component: ToolsPage }, { Icon: BsPeopleFill, @@ -184,7 +167,7 @@ export function ProblemCreateContainer() { height={15} className={cn({ 'scale-x-[-1]': - label === 'Generator' || label === 'Collaboration', + label === 'Tools' || label === 'Collaboration', 'text-color-cool-neutral-40': curTab, 'text-color-cool-neutral-70': !curTab })} diff --git a/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/ScriptSection.tsx b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/ScriptSection.tsx new file mode 100644 index 0000000000..56db1c5e9e --- /dev/null +++ b/apps/frontend/app/(client)/(main)/(create)/problem/create/_components/ScriptSection.tsx @@ -0,0 +1,10 @@ +export function ScriptSection() { + return ( +
+

+ 스크립트 +

+