diff --git a/components/DynamicQuill.tsx b/components/DynamicQuill.tsx new file mode 100644 index 000000000..5854e08a4 --- /dev/null +++ b/components/DynamicQuill.tsx @@ -0,0 +1,10 @@ +// dynamic import로 Quill 불러오는 컴포넌트 +import dynamic from 'next/dynamic'; + +// TODO : 로딩 컴포넌트 구체화 필요함. +const DynamicQuill = dynamic(() => import('react-quill'), { + ssr: false, + loading: () =>
Loading ...
, +}); + +export default DynamicQuill; diff --git a/components/Layout/Layout.tsx b/components/Layout/Layout.tsx index 59cbb3596..177777b3d 100644 --- a/components/Layout/Layout.tsx +++ b/components/Layout/Layout.tsx @@ -1,6 +1,5 @@ import { useRouter } from 'next/router'; import { useRecoilValue } from 'recoil'; -import { colorModeState } from 'utils/recoil/colorMode'; import { openCurrentMatchState } from 'utils/recoil/match'; import AdminReject from 'components/admin/AdminReject'; import AdminLayout from 'components/admin/Layout'; @@ -10,7 +9,7 @@ import Header from 'components/Layout/Header'; import HeaderStateContext from 'components/Layout/HeaderContext'; import MainPageProfile from 'components/Layout/MainPageProfile'; import Megaphone from 'components/Layout/MegaPhone'; -import StyledButton from 'components/UI/StyledButton'; +import RecruitLayout from 'components/recruit/RecruitLayout'; import Statistics from 'pages/statistics'; import useAnnouncementCheck from 'hooks/Layout/useAnnouncementCheck'; import useGetUserSeason from 'hooks/Layout/useGetUserSeason'; @@ -19,6 +18,8 @@ import useSetAfterGameModal from 'hooks/Layout/useSetAfterGameModal'; import { useUser } from 'hooks/Layout/useUser'; import useAxiosResponse from 'hooks/useAxiosResponse'; import styles from 'styles/Layout/Layout.module.scss'; +import PlayButton from './PlayButton'; +import UserLayout from './UserLayout'; type AppLayoutProps = { children: React.ReactNode; @@ -26,9 +27,7 @@ type AppLayoutProps = { export default function AppLayout({ children }: AppLayoutProps) { const user = useUser(); - const colorMode = useRecoilValue(colorModeState); const presentPath = useRouter().asPath; - const router = useRouter(); const openCurrentMatch = useRecoilValue(openCurrentMatchState); useAxiosResponse(); @@ -37,58 +36,38 @@ export default function AppLayout({ children }: AppLayoutProps) { useLiveCheck(presentPath); useAnnouncementCheck(presentPath); - const onClickMatch = () => { - router.replace('/'); - router.push(`/match`); - }; + if (!user || !user.intraId) return null; - if (!user) return null; + if (presentPath.includes('/admin')) { + if (!user.isAdmin) returnLoading ...
, -}); - export default function AnnounceEdit() { const user = useUser(); const setSnackbar = useSetRecoilState(toastState); @@ -92,7 +87,7 @@ export default function AnnounceEdit() { {content ? (Loading ...
, -}); - const tableTitle: { [key: string]: string } = { content: '내용', createdAt: '생성 시간', @@ -97,7 +92,7 @@ export default function AnnounceList() { className={styles.tableBodyItemQuill} key={index} > -Loading ...
, +}); + +interface QuillDescriptionEditorProps { + content: string; + setRecruitmentEditInfoField: (fieldName: string, value: any) => void; +} + +export default function QuillDescriptionEditor({ + content, + setRecruitmentEditInfoField, +}: QuillDescriptionEditorProps) { + const quillChangeHandler = (value: string) => { + setRecruitmentEditInfoField('content', value); + }; + + return ( +Loading ...
, -}); - const tableTitle: { [key: string]: string } = { title: '토너먼트 이름', startTime: '시작 시간', @@ -175,7 +170,7 @@ export default function TournamentEdit({ -Loading ...
, -}); - interface TournamentModalPreviewProps { tournamentEditInfo: ITournamentEditInfo; } @@ -34,7 +30,7 @@ export default function TournamentModalPreview({Loading ...
, -}); - export default function AnnouncementModal({ announcement, }: // isAttended, @@ -63,7 +58,7 @@ AnnouncementModalProps) { return (Loading ...
, -}); - export default function TournamentRegistryModal({ title, contents, @@ -186,7 +181,7 @@ export default function TournamentRegistryModal({🎮 42GG 신규 모집 🎮
X월 XX일(수) XX:XX까지
면접 대상자 발표 : XX월 XX일
활동 일정 : X월 ~ X월
프론트, 백엔드 각 N명!
👩🏻💻 나도 가능하다고? ☞ 링크 🔥
'; + +const recruitmentDetailOne = { + applicationId: 1, + startDate: '2024-03-04 12:12', + endDate: '2024-03-04 14:12', + title: '42GG 모집 1기', + contents: sampleContents, + generations: '1기', + forms: formDataOne, +}; + +const recruitmentDetailTwo = { + applicationId: 1, + startDate: '2024-12-04 00:12', + endDate: '2024-12-04 00:12', + title: '42GG 모집 2기', + contents: '지원서', + generations: '2기', + forms: formDataTwo, +}; + +const recruitmentDetailThree = { + applicationId: 1, + startDate: '2024-12-04 00:12', + endDate: '2024-12-04 00:12', + title: '42GG 모집 3기', + contents: '지원서', + generations: '3기', + forms: formDataTwo, +}; + +const recruitmentDetailFour = { + applicationId: 1, + startDate: '2024-12-04 00:12', + endDate: '2024-12-04 00:12', + title: '42GG 모집 4기', + contents: '지원서', + generations: '4기', + forms: formDataTwo, +}; + +const recruitmentDetailFive = { + applicationId: 1, + startDate: '2024-12-04 00:12', + endDate: '2024-12-04 00:12', + title: '42GG 모집 5기', + contents: '지원서', + generations: '5기', + forms: formDataTwo, +}; + +const recruitmentDetailSix = { + applicationId: 1, + startDate: '2024-12-04 00:12', + endDate: '2024-12-04 00:12', + title: '긴 내용을 테스트!!!! 42GG 6기', + contents: sampleContents.repeat(5), + generations: '6기', + forms: formDataTwo, +}; + +const recruitmentDetailSeven = { + startDate: '2024-12-04 00:12', + endDate: '2024-12-04 00:12', + title: '42GG 7기', + contents: sampleContents.repeat(5), + generations: '7기', + forms: formDataTwo, +}; + +const recruitments = [ + recruitmentDetailOne, + recruitmentDetailTwo, + recruitmentDetailThree, + recruitmentDetailFour, + recruitmentDetailFive, + recruitmentDetailSix, + recruitmentDetailSeven, +]; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + const { id } = req.query as { id: string }; + + const numberId = parseInt(id); + + if (numberId > 0 && numberId <= recruitments.length) { + res.status(200).json(recruitments[numberId - 1]); + } else { + res.status(200).json({}); + } +} diff --git a/pages/api/pingpong/recruitments/[id]/applications.ts b/pages/api/pingpong/recruitments/[id]/applications.ts new file mode 100644 index 000000000..fab25b3d6 --- /dev/null +++ b/pages/api/pingpong/recruitments/[id]/applications.ts @@ -0,0 +1,15 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + const { id } = req.query as { + id: string; + }; + + const recruitId = parseInt(id); + + if (req.method === 'POST') { + console.log('POST SUCCESS, id => ', recruitId); + console.log(req.body); + res.status(201).send('Created'); + } +} diff --git a/pages/api/pingpong/recruitments/[id]/applications/[applicationId].ts b/pages/api/pingpong/recruitments/[id]/applications/[applicationId].ts new file mode 100644 index 000000000..dcd8d12ff --- /dev/null +++ b/pages/api/pingpong/recruitments/[id]/applications/[applicationId].ts @@ -0,0 +1,146 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +const userAnswerOne = [ + { + questionId: 1, + inputType: 'TEXT', + answer: '안녕하세요 테스트입니다.', + }, + { + questionId: 2, + inputType: 'SINGLE_CHECK', + checkedList: [3], + }, + { + questionId: 3, + inputType: 'MULTI_CHECK', + checkedList: [1, 3], + }, +]; + +const userAnswerTwo = [ + { + questionId: 1, + inputType: 'SINGLE_CHECK', + checkedList: [2], + }, + { + questionId: 2, + inputType: 'TEXT', + answer: '안녕하세요 테스트입니다 222', + }, + { + questionId: 3, + inputType: 'MULTI_CHECK', + checkedList: [1, 3], + }, + { + questionId: 4, + inputType: 'MULTI_CHECK', + checkedList: [1, 2, 3], + }, +]; + +const sampleContents = + '🎮 42GG 신규 모집 🎮
X월 XX일(수) XX:XX까지
면접 대상자 발표 : XX월 XX일
활동 일정 : X월 ~ X월
프론트, 백엔드 각 N명!
👩🏻💻 나도 가능하다고? ☞ 링크 🔥
'; + +const applicationInfoOne = { + applicationId: 1, + endDate: '2024-03-04 14:12', + title: '42GG 모집 1기', + contents: sampleContents, + forms: userAnswerOne, +}; + +const applicationInfoTwo = { + applicationId: 2, + endDate: '2024-12-12 00:12', + title: '42GG 모집 2기', + contents: sampleContents, + forms: userAnswerTwo, +}; + +const applicationInfoThree = { + applicationId: 3, + endDate: '2024-12-12 00:12', + title: '42GG 모집 3기', + contents: sampleContents, + forms: userAnswerTwo, +}; +const applicationInfoFour = { + applicationId: 4, + endDate: '2024-12-12 00:12', + title: '42GG 모집 4기', + contents: sampleContents, + forms: userAnswerTwo, +}; +const applicationInfoFive = { + applicationId: 5, + endDate: '2024-12-12 00:12', + title: '42GG 모집 5기', + contents: sampleContents, + forms: userAnswerTwo, +}; + +const applicationInfoSix = { + applicationId: 6, + endDate: '2024-12-12 00:12', + title: '42GG 모집 6기', + contents: sampleContents, + forms: userAnswerTwo, +}; + +const applicationInfoSeven = { + applicationId: 7, + endDate: '2024-12-12 00:12', + title: '42GG 모집 7기', + contents: sampleContents, + forms: userAnswerTwo, +}; + +const applications = [ + applicationInfoOne, + applicationInfoTwo, + applicationInfoThree, + applicationInfoFour, + applicationInfoFive, + applicationInfoSix, + applicationInfoSeven, +]; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + const { id, applicationId } = req.query as { + id: string; + applicationId: string; + }; + + const recruitId = parseInt(id); + const applyId = parseInt(applicationId); + + if (req.method === 'GET') { + if (recruitId > 0 && recruitId <= applications.length) { + res.status(200).json(applications[recruitId - 1]); + } else { + res.status(200).json({}); + } + } + if (req.method === 'DELETE') { + console.log( + 'DELETE SUCCESS, id => ', + recruitId, + 'applicationId => ', + applyId + ); + res.status(204).send('DELETE'); + } + if (req.method === 'PATCH') { + console.log( + 'PATCH SUCCESS, id => ', + recruitId, + 'applicationId => ', + applyId + ); + console.log(req.body); + res.status(204).send('UPDATED'); + } +} diff --git a/pages/api/pingpong/recruitments/[id]/applications/[applicationId]/result.ts b/pages/api/pingpong/recruitments/[id]/applications/[applicationId]/result.ts new file mode 100644 index 000000000..c84e29a9a --- /dev/null +++ b/pages/api/pingpong/recruitments/[id]/applications/[applicationId]/result.ts @@ -0,0 +1,58 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +const before = { + title: '지원서 작성 전입니다~', + status: null, + interviewDate: null, +}; + +const beforeInterview = { + title: '면접 시간 발표 전입니다~', + status: 'PROGRESS', + interviewDate: null, +}; + +const afterInterview = { + title: '면접 시간 나왔습니다~', + status: 'INTERVIEW', + interviewDate: new Date(), +}; + +const pass = { + title: '합격 축하드립니다!', + status: 'PASS', + interviewDate: new Date(), +}; + +const applicationFail = { + title: '지원서 불합격입니다ㅠㅠ', + status: 'APPLICATION_FAIL', + interviewDate: null, +}; + +const interviewFail = { + title: '면접 불합격입니다ㅠㅠ', + status: 'INTERVIEW_FAIL', + interviewDate: new Date(), +}; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + const { id } = req.query as { id: string }; + + switch (id) { + case '1': + return res.status(200).json(before); + case '2': + return res.status(200).json(beforeInterview); + case '3': + return res.status(200).json(afterInterview); + case '4': + return res.status(200).json(pass); + case '5': + return res.status(200).json(applicationFail); + case '6': + return res.status(200).json(interviewFail); + default: + return res.status(404).json(before); + } +} diff --git a/pages/api/pingpong/recruitments/index.ts b/pages/api/pingpong/recruitments/index.ts new file mode 100644 index 000000000..d0b91874a --- /dev/null +++ b/pages/api/pingpong/recruitments/index.ts @@ -0,0 +1,98 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +const fullRecruitments1 = { + recruitments: [ + { + id: 1, + startDate: new Date(), + endDate: new Date(), + title: '42GG 모집 1기', + status: 'BEFORE', + generation: '1기', + }, + { + id: 2, + startDate: new Date(), + endDate: new Date(), + title: '42GG 모집 2기', + status: 'RECRUITING', + generation: '2기', + }, + { + id: 3, + startDate: new Date(), + endDate: new Date(), + title: '42GG 모집 3기', + status: 'AFTER', + generation: '3기', + }, + ], + totalPage: 3, +}; + +const fullRecruitments2 = { + recruitments: [ + { + id: 4, + startDate: new Date(), + endDate: new Date(), + title: '42GG 모집 4기', + status: 'BEFORE', + generation: '4기', + }, + { + id: 5, + startDate: new Date(), + endDate: new Date(), + title: '42GG 모집 5기', + status: 'RECRUITING', + generation: '5기', + }, + { + id: 6, + startDate: new Date(), + endDate: new Date(), + title: '긴 제목을 테스트!!!! 42GG 6기 ', + status: 'AFTER', + generation: '6기', + }, + ], + totalPage: 3, +}; + +const fullRecruitments3 = { + recruitments: [ + { + id: 7, + startDate: new Date(), + endDate: new Date(), + title: '42GG 모집 7기 지원하기 테스트', + status: 'BEFORE', + generation: '7기', + }, + ], + totalPage: 3, +}; + +const emptyRecruitments = { + recruitments: [], +}; + +export default function handler(req: NextApiRequest, res: NextApiResponse) { + const { page } = req.query; + if (page === '1') { + res.status(200).json(fullRecruitments1); + return; + } + if (page === '2') { + res.status(200).json(fullRecruitments2); + return; + } + if (page === '3') { + res.status(200).json(fullRecruitments3); + return; + } + + res.status(200).json(fullRecruitments1); + // res.status(200).json(emptyRecruitments); +} diff --git a/pages/recruit/[id]/apply.tsx b/pages/recruit/[id]/apply.tsx new file mode 100644 index 000000000..03be4a585 --- /dev/null +++ b/pages/recruit/[id]/apply.tsx @@ -0,0 +1,46 @@ +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; +import { useSetRecoilState } from 'recoil'; +import { applicationAlertState } from 'utils/recoil/application'; +import ApplicationForm from 'components/recruit/Application/ApplicationForm'; +import ApplicationFormHeader from 'components/recruit/Application/applicationLayout/ApplicationFormHeader'; +import ApplicationLoadingNoData from 'components/recruit/Application/ApplicationLoadingNoData'; +import useRecruitDetail from 'hooks/recruit/useRecruitDetail'; + +function Apply() { + const router = useRouter(); + const recruitId = parseInt(router.query.id as string); + const setAlertState = useSetRecoilState(applicationAlertState); + + const { data, isLoading } = useRecruitDetail(recruitId); + + useEffect(() => { + if (!isLoading && (!data || !Object.keys(data).length)) { + setAlertState({ + alertState: true, + message: '올바르지 않은 요청입니다.', + severity: 'error', + }); + return; + } + }, [isLoading, data, setAlertState]); + + if (isLoading || !data || Object.keys(data).length === 0) { + return