-
Notifications
You must be signed in to change notification settings - Fork 16
feat(fe): make generator, checker, validator page #3491
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
Open
ojongii
wants to merge
11
commits into
main
Choose a base branch
from
t2631-make-generator-checker-validator-page
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
36d20ff
feat(fe): make generator, checker, validator page
ojongii dec47f8
Merge branch 'main' into t2631-make-generator-checker-validator-page
sONg20NOW 03beac4
chore(fe): apply feedback and change border styles
ojongii bf7607a
Merge branch 't2631-make-generator-checker-validator-page' of https:/…
ojongii 2ae5c21
Merge branch 'main' into t2631-make-generator-checker-validator-page
ojongii f05fc1d
feat(fe): unify pages
ojongii 3a7d2d1
chore(fe): apply feedback
ojongii aa203ac
Merge branch 'main' into t2631-make-generator-checker-validator-page
ojongii 98369bf
chore(fe): use info-icon-gray.svg
ojongii 7bb1ca3
Merge branch 't2631-make-generator-checker-validator-page' of https:/…
ojongii 7b9d0b4
fix(fe): accept one file only
ojongii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
14 changes: 13 additions & 1 deletion
14
apps/frontend/app/(client)/(main)/(create)/problem/create/_components/CheckerPage.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,15 @@ | ||
| import { FileUploadSection } from './FileUploadSection' | ||
|
|
||
| export function CheckerPage() { | ||
| return <div>This is Checker page</div> | ||
| return ( | ||
| <FileUploadSection | ||
| title="특수 채점" | ||
| description="부동소수 오차, 특수 채점 등의 출력 비교 로직을 업로드 해주세요" | ||
| accept=".cpp" | ||
| emptyMessages={[ | ||
| '업로드된 특수 채점 프로그램이 없습니다.', | ||
| '기본 모드에서는 없이도 배포할 수 있으며, 커스텀 채점이 필요하면 추가해주세요.' | ||
| ]} | ||
| /> | ||
| ) | ||
| } |
165 changes: 165 additions & 0 deletions
165
apps/frontend/app/(client)/(main)/(create)/problem/create/_components/FileUploadSection.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| import infoIcon from '@/public/icons/file-info-gray.svg' | ||
| import fileIcon from '@/public/icons/file_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 } from 'react' | ||
| import { useState, useRef } from 'react' | ||
|
|
||
| interface UploadedFile { | ||
| id: string | ||
| name: string | ||
| size: string | ||
| } | ||
|
|
||
| interface FileUploadSectionProps { | ||
| title: string | ||
| description: string | ||
| emptyMessages: string[] | ||
| accept: string | ||
| } | ||
|
|
||
| const FORMAT_EXAMPLES = [ | ||
| 'Generator : generator.cpp / generator.py', | ||
| 'Validator : validator.cpp (testlib 등)', | ||
| 'Checker : checker.cpp (특수 채점)' | ||
| ] | ||
|
|
||
| export function FileUploadSection({ | ||
| title, | ||
| description, | ||
| emptyMessages, | ||
| accept | ||
| }: FileUploadSectionProps) { | ||
| const [files, setFiles] = useState<UploadedFile[]>([]) | ||
| const fileInputRef = useRef<HTMLInputElement>(null) | ||
|
|
||
| const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { | ||
| const selectedFiles = e.target.files | ||
| if (!selectedFiles) { | ||
| return | ||
| } | ||
|
|
||
| const allowedExtensions = accept.split(',').map((ext) => ext.trim()) | ||
|
|
||
| const newFiles: UploadedFile[] = Array.from(selectedFiles) | ||
| .filter((file) => | ||
| allowedExtensions.some((ext) => file.name.endsWith(ext)) | ||
| ) | ||
| .map((file) => { | ||
| const uniqueId = `${file.name}-${file.size}-${Date.now()}-${Math.random().toString(36).substring(2, 7)}` | ||
|
|
||
| return { | ||
| id: uniqueId, | ||
| name: file.name, | ||
| size: `${(file.size / 1024).toFixed(1)}KB` | ||
| } | ||
| }) | ||
|
|
||
| if (newFiles.length === 0 && selectedFiles.length > 0) { | ||
| alert(`${accept} 파일만 업로드 가능합니다.`) | ||
| return | ||
| } | ||
|
|
||
| setFiles((prev) => [...prev, ...newFiles]) | ||
| if (fileInputRef.current) { | ||
| fileInputRef.current.value = '' | ||
| } | ||
| } | ||
|
|
||
| const handleDelete = (id: string) => { | ||
| setFiles((prev) => prev.filter((file) => file.id !== id)) | ||
| } | ||
|
|
||
| return ( | ||
| <div className="border-color-cool-neutral-90 rounded-[16px] border px-6 py-7"> | ||
| <div className="flex justify-between border-b pb-5"> | ||
| <div> | ||
| <p className="text-head5_sb_24 mb-1">{title}</p> | ||
| <p className="text-body2_m_14 text-color-cool-neutral-40"> | ||
| {description} | ||
| </p> | ||
| </div> | ||
| <input | ||
| type="file" | ||
| ref={fileInputRef} | ||
| onChange={handleFileChange} | ||
| accept={accept} | ||
| multiple | ||
| className="hidden" | ||
| /> | ||
| <button | ||
| onClick={() => fileInputRef.current?.click()} | ||
| className="border-primary-light text-primary text-sub4_sb_14 flex h-fit items-center gap-[6px] rounded-[8px] border-[1.4px] px-3 py-[10px] transition-colors hover:bg-blue-50" | ||
| > | ||
| <Image src={uploadIcon} alt="upload" width={20} height={20} /> | ||
| 파일 업로드 | ||
| </button> | ||
| </div> | ||
|
|
||
| <div className="my-5 min-h-[160px]"> | ||
| {files.length === 0 ? ( | ||
| <div className="bg-color-neutral-99 flex flex-col items-center rounded-[12px] py-20 text-center"> | ||
| <Image | ||
| src={infoIcon} | ||
| alt="info" | ||
| width={24} | ||
| height={24} | ||
| className="mb-2" | ||
| /> | ||
| {emptyMessages.map((msg, idx) => ( | ||
| <p | ||
| key={idx} | ||
| className="text-body1_m_16 text-color-cool-neutral-50" | ||
| > | ||
| {msg} | ||
| </p> | ||
| ))} | ||
| </div> | ||
| ) : ( | ||
| <div className="flex flex-col gap-3"> | ||
| {files.map((file) => ( | ||
| <div | ||
| key={file.id} | ||
| className="flex items-center justify-between rounded-[12px] bg-white p-4 shadow-[0_4px_20px_0_rgba(53,78,116,0.10)]" | ||
| > | ||
| <div className="flex gap-4"> | ||
| <span className="bg-color-neutral-99 flex h-12 w-12 items-center justify-center rounded-[6.4px] border"> | ||
| <Image src={fileIcon} alt="file" width={20} height={20} /> | ||
|
sONg20NOW marked this conversation as resolved.
Outdated
|
||
| </span> | ||
| <div> | ||
| <p className="text-sub1_sb_18">{file.name}</p> | ||
| <p className="text-sub4_sb_14 text-color-cool-neutral-40"> | ||
| {file.size} | ||
| </p> | ||
| </div> | ||
| </div> | ||
| <button | ||
| onClick={() => handleDelete(file.id)} | ||
| className="border-color-neutral-90 bg-color-neutral-99 hover:bg-color-neutral-95/80 flex h-9 w-12 items-center justify-center rounded-full border transition-all" | ||
| > | ||
| <Image | ||
| src={trashcanIcon} | ||
| alt="trash" | ||
| width={16} | ||
| height={16} | ||
| /> | ||
| </button> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
|
|
||
| <p className="text-sub1_sb_18 mb-2">포맷 예시</p> | ||
|
sONg20NOW marked this conversation as resolved.
Outdated
|
||
| <div className="bg-color-neutral-99 border-color-cool-neutral-90 flex flex-col gap-2 rounded-[8px] border p-4"> | ||
| {FORMAT_EXAMPLES.map((example, index) => ( | ||
| <div key={index} className="flex items-center gap-2"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bullet이랑 텍스트 사이 너비가 피그마 상보다 좁아요! 이 페이지의 bullet 크기가 8x8이 아니어서 그런 것 같습니당! |
||
| <span className="bg-color-neutral-30 h-1 w-1 shrink-0 rounded-full" /> | ||
| <p className="text-body3_r_16 text-color-neutral-30">{example}</p> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
25 changes: 12 additions & 13 deletions
25
apps/frontend/app/(client)/(main)/(create)/problem/create/_components/GeneratorPage.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <div>This is Generator page</div> | ||
| return ( | ||
| <FileUploadSection | ||
| title="테스트 생성" | ||
| description="테스트 입력을 생성하는 프로그램 및 스크립트를 업로드하세요" | ||
| accept=".cpp, .py" | ||
| emptyMessages={[ | ||
| '업로드 된 테스트 생성 프로그램이 없습니다.', | ||
| '커스텀 채점이 필요하면 추가해주세요.' | ||
| ]} | ||
| /> | ||
| ) | ||
| } |
14 changes: 13 additions & 1 deletion
14
apps/frontend/app/(client)/(main)/(create)/problem/create/_components/ValidatorPage.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,15 @@ | ||
| import { FileUploadSection } from './FileUploadSection' | ||
|
|
||
| export function ValidatorPage() { | ||
| return <div>This is Validator page</div> | ||
| return ( | ||
| <FileUploadSection | ||
| title="입력 검증" | ||
| description="잘못된 테스트를 걸러내는 용도의 입력 제약조건 검증하세요" | ||
| accept=".cpp" | ||
| emptyMessages={[ | ||
| '업로드된 입력 검증 프로그램이 없습니다.', | ||
| '기본 모드에서는 없이도 배포할 수 있으며, 커스텀 채점이 필요하면 추가해주세요.' | ||
| ]} | ||
| /> | ||
| ) | ||
| } |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.