From 1909b3f054035ff1fb10030b46b9c806368a8a78 Mon Sep 17 00:00:00 2001 From: Eonseok Jeon <121864459+eonseok-jeon@users.noreply.github.com> Date: Tue, 28 Jan 2025 22:36:35 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A9=94=EC=9D=B4=EC=BB=A4=EC=8A=A4=2036?= =?UTF-8?q?=EA=B8=B0=20=EB=AA=A8=EC=A7=91=20=EC=A0=84=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=20(#476)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Refactor] context provider를 컴포넌트로 분리 (#437) * refactor: device type provider component로 분리 * chore: alias 추가 * refactor: useDeviceType 적용 * refactor: recruiting info provider component로 분리 * refactor: useRecruitingInfo 적용 * refactor: theme provider component로 분리 * refactor: useTheme 적용 * docs: 기존 파일 제거 * chore: 에러 메세지 수정 * chore: 변수명 통일 * chore: 불필요한 export 제거 * [Refactor] 번들 크기 개선 - 1 (#434) * buil: bundle size visualizer 설치 * refactor: lottie 관련 필요 기능만 구현하기 * build: light 버전 import * chore: lottie file size 줄이기 * [Refactor] 번들 크기 개선 - 2 (#435) * build: date-fns locale import 범위 좁히기 * chore: 코드 복구 * [Refactor] 번들 크기 개선 - 3 (#432) * refactor: lazy loading 적용 * refactor: dialogs들에도 lazy import 적용 * build: manual chunk 적용 * chore: visualizer 자동 open option 제거 * [Feat] 테스트 코드 초기 세팅 (#439) * build: library 설치 * feat: lint 설정 * feat: config 설정 * feat: custom render 생성 * build: library 추가 설치 * feat: test setup 파일 추가 * chore: alias 추가 * fix: custon render option 속성 optional 하게 받기 * chore: 주석 제거 * build: test library 버전업 * fix: custom render type 수정 * feat: custom render에 memory router 추가 * feat: custom render에 query client provider 추가 * fix: fillStyle을 null로 설정할 수 없는 에러 해결 * fix: test 환경에 modal div 생성 * fix: custom render에 form provider 추가 * fix: window 함수 선언 * docs: README 수정 * [Chore] reviewer에 주용이 추가 (#443) * chore: 주용이 추가 * [Refactor] token 필요없는 fetch custom hook 만들기 (#445) * feat: 기존 axios instace -> fetch instance로 대체 * refactor: 기존 코드 fetch instance로 변경 * refactor: headers type 제한 * feat: instace option method type 제한 * [Refactor] ApplyPage 로직 분리 - 1 (#441) * chore: 관심사 끼리 분류 * refactor: 페이지 이탈 alert custom hook으로 분리 * refactor: isReview 전역 변수로 빼기 * refactor: apply page loading 제거 * feat: useDialog hook 생성 * refactor: useDialog 적용 * chore: dialog들 Form Provider 바깥으로 빼기 * chore: 오타 제거 * refactor: isReview 전역변수 제거 * feat: useEventListener custom hook 제작 * refactor: useEventListener 적용 * [Feat] env 개발 환경 분리하기 (#448) * feat: env 분리 * feat: build 명령어 분리 * fix: build 명령어 수정 * [Feat] 환경변수를 활용한 동적 메타태그 구현 (#455) * install: react-helmet-async 설치 * feat: provider 추가 * feat: index.html에서 helmet으로 이동 * feat: URL 추가 * fix: isMakers context->env로 가져오기 * chore: 테스트용 연산자 제거 * fix: 환경변수 말고 MODE 활용 * fix: useDate 내부 isMakers도 수정 * [Refactor] fetch instance 기능 보강 (#450) * feat: error 처리 로직 세분화를 위한 custom error 생성 * feat: params 처리 * feat: form data 처리 * refactor: instance 적용 * chore: fetcher로 이름 변경 * [Feat] browserslist 추가 (#452) * feat: browserslist 추가 * feat: 지원되는 browser 파악하는 regex 생성 * design: 지원 안 되는 브라우저 안내 페이지 생성 * feat: browser 판단 로직 구현 * fix: supported browsers ts로 변경 * [Design] 지원 안 되는 브라우저 안내 페이지 퍼블리싱 (#464) * design: 지원되지 않는 페이지 퍼블리싱 * fix: icon들 png -> svg 변경 * desing: 반응형 구현 * design: 중앙 정렬 * fix: 문의하기 링크 수정 * design: 높이 수정 * design: header 추가 * design: layout 추가 * design: css 수정 * design: sopt logo 변경 * fix: 비교 연산자 수정 * [Feat] 지원 안 되는 브라우저 안내 페이지 pre render 하기 (#465) * feat: unsupported page html 파일 생성하기 * fix: 지원 안되는 브라우저 경로 수정 * design: 문장 사이 공백 추가 * refactor: script 위치 수정 * design: css 파일 추출 * refactor: build 명령어 수정에 따른 html, css 파일 수정 * refactor: renderToStaticMarkup으로 변경 * feat: gitignore에 unsupported.html 추가 * design: css 수정 * [Refactor] lottie-react 재도입 및 patch를 이용한 번들 사이즈 감소 (#466) * chore: 사용 안되는 컴포넌트 제거 * refactor: lottie-react로 변경 * refactor: patch를 이용하여 lottie-web/light 버전 이용 * docs: read me 수정 * [Feat] MSW 도입 (#453) * install: msw 설치 * feat: generate worker script * feat: msw 기본 코드 테스트 * chore: amplitude 주석처리 * chore: 주석 해제 * [Refactor] date-fns 라이브러리 제거 (#469) * feat: isBefore, isAfter 함수 구현 * feat: differenceInSeconds 함수 구현 * feat: subMinutes 함수 구현 * feat: format 함수 구현 * remove: Intl 덜어내기 * feat: string -> Date 형변환 처리 추가 * remove: test 용 파일 삭제 * remove: date-fns 라이브러리 삭제 * feat: EEEE RegExp 추가 * fix: toDate 함수 분리 * fix: 요일 인덱스 버그 픽스 * fix: toDate 적용 및 throw Error -> console error로 수정 * fix: throw Error 코드 복구 * fix: chrome 판단 regex 수정 (#470) * [Refactor] OpenAPI를 이용하여 api interface 추출하기 (#472) * feat: openapi spec을 이용한 api interface 생성 * feat: api interface를 가지는 fetcher 생성 * feat: 각 api에 맞는 interface 추출하기 위한 명령어 작성 * chore: 명령어 실행 * feat: gitattributes 설정 * fix: 명령어 폴더명 수정 * fix: 빌드 에러 수정 * [Refactor] Amplitude tracking 로직 컴포넌트로 분리 (#473) * feat: amplitude event track component 생성 * feat: Button component에 Amplitude event track component 감싸기 * refactor: amplitude event track component 적용 * fix: build error * fix: build error * fix: 지원되지 않는 브라우저 로직 수정 * 배포 전 점검 (#475) * fix: build 명령어 수정 * fix: 오티 날짜 수정 * refactor: 기수 배열 자동으로 계산 * fix: 이름, 이메일, 전화번호 자동으로 안 채워지는 에러 해결 * fix: 파일 업로드 실패 시 업로드 상태 초기화 * refactor: 불필요한 props 제거 * fix: 임시저장 안 불러와지는 에러 해결 * fix: makers일 경우 knownPath에 빈 값 실어보내기 * fix: 빌드 에러 해결 * fix: 서류 결과 확인 에러 수정 * fix: 최종 결과 페이지 날짜 에러 해결 * chore: 코드 원상복구 --------- Co-authored-by: lydiacho <81505421+lydiacho@users.noreply.github.com> --- package.json | 18 +++++------ .../components/CommonSection/index.tsx | 18 +++++++---- .../components/DefaultSection/index.tsx | 17 ++++++---- .../components/DefaultSection/utils.ts | 9 ++++++ .../ApplyPage/components/FileInput/index.tsx | 4 +++ .../components/PartSection/index.tsx | 30 +++++++---------- src/views/ApplyPage/constant.ts | 5 +-- src/views/ApplyPage/index.tsx | 29 ++++++----------- .../ResultPage/components/FinalResult.tsx | 2 +- .../ResultPage/components/ScreeningResult.tsx | 27 +++++++--------- src/views/ReviewPage/index.tsx | 32 +++---------------- 11 files changed, 87 insertions(+), 104 deletions(-) create mode 100644 src/views/ApplyPage/components/DefaultSection/utils.ts diff --git a/package.json b/package.json index ecfd68e0..d0523fca 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,14 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "NODE_ENV=makers vite --mode makers", - "dev:sopt": "NODE_ENV=sopt vite --mode sopt", - "dev:dev": "vite --mode development", - "build:ssg": "tsc && vite build --mode makers --ssr src/views/UnsupportedPage/pre-render.tsx && node dist/pre-render.js", - "build:ssg:sopt": "tsc && vite build --mode sopt --ssr src/views/UnsupportedPage/pre-render.tsx && node dist/pre-render.js", - "build": "yarn build:ssg && tsc && NODE_ENV=makers vite build --mode makers", - "build:sopt": "yarn build:ssg:sopt && tsc && NODE_ENV=sopt vite build --mode sopt", - "build:dev": "tsc && vite build --mode development", + "dev:sopt": "NODE_ENV=production vite --mode sopt", + "dev:makers": "NODE_ENV=production vite --mode makers", + "dev": "yarn dev:makers", + "unsupported:makers": "tsc && vite build --mode makers --ssr src/views/UnsupportedPage/pre-render.tsx && node dist/pre-render.js", + "unsupported:sopt": "tsc && vite build --mode sopt --ssr src/views/UnsupportedPage/pre-render.tsx && node dist/pre-render.js", + "build:sopt": "yarn unsupported:sopt && tsc && NODE_ENV=production vite build --mode sopt", + "build:makers": "yarn unsupported:makers && tsc && NODE_ENV=production vite build --mode makers", + "build": "yarn build:makers", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "test": "vitest", @@ -97,4 +97,4 @@ "public" ] } -} +} \ No newline at end of file diff --git a/src/views/ApplyPage/components/CommonSection/index.tsx b/src/views/ApplyPage/components/CommonSection/index.tsx index f676a217..e70ca3cf 100644 --- a/src/views/ApplyPage/components/CommonSection/index.tsx +++ b/src/views/ApplyPage/components/CommonSection/index.tsx @@ -1,22 +1,28 @@ import Textarea from '@components/Textarea'; import { useDeviceType } from 'contexts/DeviceTypeProvider'; import { sectionContainerVar, sectionTitleVar } from 'views/ApplyPage/style.css'; -import { Answers, Questions } from 'views/ApplyPage/types'; +import { Answers } from 'views/ApplyPage/types'; import FileInput from '../FileInput'; import Info from '../Info'; import LinkInput from '../LinkInput'; +import useGetDraft from 'views/ApplyPage/hooks/useGetDraft'; +import useGetQuestions from 'views/ApplyPage/hooks/useGetQuestions'; interface CommonSectionProps { isReview?: boolean; refCallback: (elem: HTMLSelectElement) => void; - questions?: Questions[]; - commonQuestionsDraft?: Answers[]; } -const CommonSection = ({ refCallback, questions, commonQuestionsDraft, isReview = false }: CommonSectionProps) => { +const CommonSection = ({ refCallback, isReview = false }: CommonSectionProps) => { const { deviceType } = useDeviceType(); - const commonQuestionsById = commonQuestionsDraft?.reduce( + const { draftData } = useGetDraft(); + const { applicant, commonQuestions } = draftData?.data || {}; + + const { questionsData } = useGetQuestions(applicant); + const { commonQuestions: questions } = questionsData?.data || {}; + + const commonQuestionsById = commonQuestions?.reduce( (acc, draft) => { acc ? (acc[draft.id] = draft) : undefined; return acc; @@ -27,7 +33,7 @@ const CommonSection = ({ refCallback, questions, commonQuestionsDraft, isReview return (

공통 질문

- {questions?.map(({ urls, value, id, charLimit, isFile, placeholder, optional }) => { + {questions?.questions.map(({ urls, value, id, charLimit, isFile, placeholder, optional }) => { const draftItem = commonQuestionsById?.[id]; const defaultValue = draftItem ? draftItem.answer.answer : ''; const defaultFile = { id, file: draftItem?.answer.file, fileName: draftItem?.answer.fileName }; diff --git a/src/views/ApplyPage/components/DefaultSection/index.tsx b/src/views/ApplyPage/components/DefaultSection/index.tsx index 8334b4c6..b438d8b0 100644 --- a/src/views/ApplyPage/components/DefaultSection/index.tsx +++ b/src/views/ApplyPage/components/DefaultSection/index.tsx @@ -8,7 +8,6 @@ import { VALIDATION_CHECK } from '@constants/validationCheck'; import { useDeviceType } from 'contexts/DeviceTypeProvider'; import { SELECT_OPTIONS } from 'views/ApplyPage/constant'; import { sectionTitleVar } from 'views/ApplyPage/style.css'; -import { Applicant } from 'views/ApplyPage/types'; import Postcode from './components/Postcode'; import { DEFAULT_PROFILE } from './constants'; @@ -24,6 +23,9 @@ import { profileWrapperVar, sectionContainerVar, } from './style.css'; +import { getMostRecentSeasonArray } from './utils'; +import useGetDraft from 'views/ApplyPage/hooks/useGetDraft'; +import useDate from '@hooks/useDate'; interface ProfileImageProps { disabled: boolean; @@ -106,15 +108,18 @@ const ProfileImage = ({ disabled, pic, deviceType }: ProfileImageProps) => { }; interface DefaultSectionProps { - isMakers?: boolean; isReview?: boolean; refCallback?: (elem: HTMLSelectElement) => void; - applicantDraft?: Applicant; } -const DefaultSection = ({ isMakers, refCallback, applicantDraft, isReview = false }: DefaultSectionProps) => { +const DefaultSection = ({ refCallback, isReview = false }: DefaultSectionProps) => { const { deviceType } = useDeviceType(); + const { isMakers } = useDate(); + const { draftData } = useGetDraft(); + + const { applicant } = draftData?.data || {}; const { + season, address, birthday, college, @@ -128,7 +133,7 @@ const DefaultSection = ({ isMakers, refCallback, applicantDraft, isReview = fals pic, univYear, leaveAbsence, - } = applicantDraft || {}; + } = applicant || {}; return (
@@ -247,7 +252,7 @@ const DefaultSection = ({ isMakers, refCallback, applicantDraft, isReview = fals label="이전 기수 활동 여부 (제명 포함)" name="mostRecentSeason" placeholder="가장 최근에 활동했던 기수를 선택해주세요." - options={isMakers ? SELECT_OPTIONS.mostRecentSeason.slice(1) : SELECT_OPTIONS.mostRecentSeason} + options={getMostRecentSeasonArray(season || 0, isMakers || false)} required size="lg" disabled={isReview} diff --git a/src/views/ApplyPage/components/DefaultSection/utils.ts b/src/views/ApplyPage/components/DefaultSection/utils.ts new file mode 100644 index 00000000..73874ec6 --- /dev/null +++ b/src/views/ApplyPage/components/DefaultSection/utils.ts @@ -0,0 +1,9 @@ +export const getMostRecentSeasonArray = (season: number, isMakers: boolean) => { + const array = Array.from({ length: 10 }, (_, i) => String(season - i - 1)); + + if (!isMakers) { + array.unshift('해당사항 없음'); + } + + return array; +}; diff --git a/src/views/ApplyPage/components/FileInput/index.tsx b/src/views/ApplyPage/components/FileInput/index.tsx index aebe7e0e..b73717cb 100644 --- a/src/views/ApplyPage/components/FileInput/index.tsx +++ b/src/views/ApplyPage/components/FileInput/index.tsx @@ -70,6 +70,10 @@ const FileInput = ({ section, id, isReview, disabled, defaultFile }: FileInputPr }, (error) => { console.error(error); + uploadTask.cancel(); // 업로드 취소 + setUploadPercent(-1); // 진행 상태 초기화 + setFileName(''); // 파일 이름 초기화 + handleDeleteFileValue(); // 파일 값 제거 setError(`file${id}`, { type: 'unknownError', message: VALIDATION_CHECK.fileInput.errorTextUnknownError }); }, () => { diff --git a/src/views/ApplyPage/components/PartSection/index.tsx b/src/views/ApplyPage/components/PartSection/index.tsx index 3b6c0864..dd4fe0a8 100644 --- a/src/views/ApplyPage/components/PartSection/index.tsx +++ b/src/views/ApplyPage/components/PartSection/index.tsx @@ -4,40 +4,34 @@ import SelectBox from '@components/Select'; import Textarea from '@components/Textarea'; import { useDeviceType } from 'contexts/DeviceTypeProvider'; import { sectionContainerVar, sectionTitleVar } from 'views/ApplyPage/style.css'; -import { Answers, Questions } from 'views/ApplyPage/types'; +import { Answers } from 'views/ApplyPage/types'; import FileInput from '../FileInput'; import Info from '../Info'; import LinkInput from '../LinkInput'; +import useGetDraft from 'views/ApplyPage/hooks/useGetDraft'; +import useGetQuestions from 'views/ApplyPage/hooks/useGetQuestions'; interface PartSectionProps { isReview?: boolean; refCallback: (elem: HTMLSelectElement) => void; - part?: string; - questions?: { - part: string; - recruitingQuestionTypeId: number; - questions: Questions[]; - }[]; - partQuestionsDraft?: Answers[]; - questionTypes?: { id: number; type: string; typeKr: string; typeLegacy: null }[]; } -const PartSection = ({ - refCallback, - part, - questions, - partQuestionsDraft, - questionTypes, - isReview = false, -}: PartSectionProps) => { +const PartSection = ({ refCallback, isReview = false }: PartSectionProps) => { const { deviceType } = useDeviceType(); const { getValues } = useFormContext(); + const { draftData } = useGetDraft(); + const { applicant, partQuestions } = draftData?.data || {}; + const { part } = applicant || {}; + + const { questionsData } = useGetQuestions(applicant); + const { partQuestions: questions, questionTypes } = questionsData?.data || {}; + const partOptions = questionTypes?.sort((a, b) => a.id - b.id).map(({ typeKr }) => typeKr); const selectedPart: string = getValues('part'); const filteredQuestions = questions?.find((item) => item.part === selectedPart)?.questions; - const partQuestionsById = partQuestionsDraft?.reduce( + const partQuestionsById = partQuestions?.reduce( (acc, draft) => { acc ? (acc[draft.id] = draft) : undefined; return acc; diff --git a/src/views/ApplyPage/constant.ts b/src/views/ApplyPage/constant.ts index 29b476dc..1d61b414 100644 --- a/src/views/ApplyPage/constant.ts +++ b/src/views/ApplyPage/constant.ts @@ -1,3 +1,5 @@ +const isMakers = import.meta.env.MODE === 'makers'; + export const APPLY_INFO = { sections: [ { @@ -13,7 +15,7 @@ export const APPLY_INFO = { id: 1, content: [ { - text: '9월 28일 토요일 OT(오프라인 예정)에 불참 시 지원이 불가하오니 자세히 확인 바랍니다.', + text: `${isMakers ? '2월 23일 일요일 ' : '9월 28일 토요일 '}OT(오프라인 예정)에 불참 시 지원이 불가하오니 자세히 확인 바랍니다.`, weight: 'normal', }, ], @@ -51,7 +53,6 @@ export const SELECT_OPTIONS = { univYearMakers: ['1학년', '2학년', '3학년', '4학년', '수료 ‧ 유예 ‧ 졸업'], leaveAbsence: ['재학', '휴학 ‧ 수료 ‧ 유예'], leaveAbsenceMakers: ['재학', '휴학 ‧ 수료 ‧ 유예 ‧ 졸업'], - mostRecentSeason: ['해당사항 없음', '34', '33', '32', '31', '30', '29', '28', '27', '26', '25'], knownPath: [ 'SOPT 페이스북 페이지', 'SOPT 인스타그램', diff --git a/src/views/ApplyPage/index.tsx b/src/views/ApplyPage/index.tsx index 08b4c458..72c2665a 100644 --- a/src/views/ApplyPage/index.tsx +++ b/src/views/ApplyPage/index.tsx @@ -27,6 +27,7 @@ import { buttonWrapper, container, formContainerVar } from './style.css'; import type { ApplyRequest } from './types'; import useDialog from '@hooks/useDialog'; +import BigLoading from 'views/loadings/BigLoding'; const DraftDialog = lazy(() => import('views/dialogs').then(({ DraftDialog }) => ({ default: DraftDialog }))); const PreventApplyDialog = lazy(() => @@ -59,12 +60,8 @@ const ApplyPage = ({ onSetComplete }: ApplyPageProps) => { const minIndex = isInView.findIndex((value) => value === true); // 4. 데이터 불러오기 - const { draftData } = useGetDraft(); - const { - applicant: applicantDraft, - commonQuestions: commonQuestionsDraft, - partQuestions: partQuestionsDraft, - } = draftData?.data || {}; + const { draftData, draftIsLoading } = useGetDraft(); + const { applicant: applicantDraft } = draftData?.data || {}; const { questionsData } = useGetQuestions(applicantDraft); const { commonQuestions, partQuestions, questionTypes } = questionsData?.data || {}; @@ -263,7 +260,7 @@ const ApplyPage = ({ onSetComplete }: ApplyPageProps) => { birthday, college, gender, - knownPath, + knownPath: knownPath ? knownPath : '', leaveAbsence, major, mostRecentSeason, @@ -281,6 +278,8 @@ const ApplyPage = ({ onSetComplete }: ApplyPageProps) => { handleShowSubmitDialog(); }; + if (draftIsLoading) return ; + return ( <> @@ -313,19 +312,9 @@ const ApplyPage = ({ onSetComplete }: ApplyPageProps) => { name="apply-form" onSubmit={handleSubmit(handleApplySubmit)} className={formContainerVar[deviceType]}> - - - + + +