Skip to content

Commit

Permalink
Feat/#74 사인 등록 페이지 구현 (#75)
Browse files Browse the repository at this point in the history
* 7, 8주차 산출물 (#70)

* Update README.md

* Refactor/#47 6주차 코드리뷰 리팩토링 (#48)

* refactor: separate style and prop-related constants

* refactor: remove lambda function

* refactor: restructure SignUp and RecruitmentHeader components

* feat: separate Button and Text component of SignIn

* feat: add SignUpText component

* refactor: remove auth page's barrel file

* Feat/#42, #43 지원자 목록 페이지 및 팝업 구현 (#51)

* feat: 지원자 목록 페이지 구현

* feat: 지원자 목록 페이지 스토리북 생성

* refactor: ApplicantList 테이블 컴포넌트 분리

* feat: Applicants path 설정

* feat: 지원자 목록 페이지에 계약 관련 팝업 추가

* fix: 이미지 경로 수정 및 불필요한 태그 제거

* refactor: MyAccount 페이지 구조 변경 및 CompanyRecruitments로 파일명 변경

- 기존 MyAccount 페이지를 CompanyRecruitments로 이름 변경
- 새로운 MyAccount 페이지 구현을 위해 기존 페이지의 역할 변경

* refactor: visaRegistration 관련 파일 구조 변경

- visaRegistration 페이지를 src/pages/employee에서 src/pages로 이동
- 관련 기능을 src/features/employee/visaRegistration에서 src/features/visaRegistration으로 이동

* feat: Table 컴포넌트 구현

* feat: 변경된 고용주 마이페이지 구현

* refactor: 회사 관련 공통 기능을 features/companies로 이동 및 CompanyInfo 수정

* refactor: CompanyRecruitments 페이지 이름을 MyCompany로 변경

* feat: EmployerMyAccount path 설정

* refactor: 불필요한 코드 삭제 및 폴더명 일관성 있게 변경

* Feat/#50 Select 컴포넌트 구현 (#53)

* feat: add Select component

* feat: add useGlobalSelect and useSelect

* refactor: move directory

* refactor: EmployerMyAccount 페이지에서 mock 데이터 분리 및 코드 정리

* refactor: visaRegistration 및 applicants 페이지의 mock, style 파일 분리

* refactor: RecruitmentList 컴포넌트 리팩토링 및 RecruitmentsTable 분리

* refactor: CompanyInfo 반응형 디자인 수정

* refactor: 외국인 번호 및 비자 발급 일자 등록 페이지 스타일 수정

* refactor: Header 컴포넌트의 닉네임 버튼을 사용자 프로필 이미지로 변경

* Refactor/#54 Modal 컴포넌트 재설계 (#55)

* chore: add loadable component package

* feat: implement modal management system with context and dynamic loading

* refactor: 코드 리뷰 반영

- formValid를 useMemo로 관리
- validateForeignerNumber 함수를 별도 파일로 분리

* Feat/#56 메인 페이지 API 연동 (#57)

* chore: setting mockServiceWorker

* feat: add useFetchRecruitments hooks and recruitmentsMockHandler

* feat: add useFetchSlides hooks and slidesMockHandler

* feat: add Spinner component

* feat: add AsyncBoundary component

* chore: add msw-storybook-addon

* Feat/#58 OAuth 구글 로그인 구현 (#59)

* feat: add useGoogleOAuth hook

* feat: add Loading page

* chore: add MemoryRouter to decorators

* Feat/#60 가입자 정보 선택 API 연동 (#61)

* feat: add useRegister hook

* fix: change role prop value

* style: Button 컴포넌트 Props 이름변경 theme->design

* feat: 근로자마이페이지 아이콘 설정

* feat: 근로자 마이페이지 구현

* feat: 근로자마이페이지 라우터 설정

* style: Button props 이름 변경

* feat: msw 세팅 및 API path 작성

* feat: 구인글 등록 API 연결 및 msw 세팅

* fix: 구인글 업로드 mock 핸들러 수정

* feat: 근로자 마이페이지 mock 핸들러 추가

* feat: 근로자 마이페이지 API 연결 및 msw 설정

* feat: 이력서 페이지 구현 (#63)

- react-hook-form 을 사용했습니다.
- api 명세서에 맞게 이름,주소,번호,경력,자기소개,한국어실력을 필수값으로 받게 했습니다.

Co-authored-by: kangkibong <[email protected]>

* fix: change button prop

* feat: add GitHub Actions workflow for linting and type checking

---------

Co-authored-by: yimsebin <[email protected]>
Co-authored-by: Kim Jian <[email protected]>
Co-authored-by: KimJi-An <[email protected]>
Co-authored-by: LEE YONGJIN <[email protected]>

* docs: update README.md

* docs: update README.md

* docs: update README.md

* chore: react-signature-canvas 설치

* feat: 사인 등록 페이지 구현
- 사인을 이미지를 파일로 만든 후 서버로 전송합니다.
- 요청 로직도 같이 구현했습니다.
- 요청 완료 후 리다이렉트는 로그인 이후 구현

* feat: 사인 등록 페이지 api 연동(msw)

---------

Co-authored-by: kangkibong <[email protected]>
Co-authored-by: yimsebin <[email protected]>
Co-authored-by: Kim Jian <[email protected]>
Co-authored-by: KimJi-An <[email protected]>
  • Loading branch information
5 people authored Oct 28, 2024
1 parent f4545c6 commit 21c3b41
Show file tree
Hide file tree
Showing 10 changed files with 313 additions and 40 deletions.
88 changes: 53 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,60 @@
# 🍪 내가 먹은 쿠키 - 18조 FE

## 🙋‍♂️ 6주차 코드리뷰 질문
- 하나의 파일 내에서 함수 표현식과 선언식을 같이 사용해도 되는지 궁금합니다.
- 기존 Weekly 브랜치에 components 경로에 `RoleModal` 컴포넌트가 있다고 가정하면, 해당 Weekly브랜치를 통해 새로 분기된 Feat 브랜치에 기존 components에 `RoleModal` 컴포넌트를 features 경로에 옮기고 두 브랜치를 머지하게되면 두개의 파일이 생기게 됩니다. 이를 해결하려면 어떻게 해야하나요?
## 🙋‍♂️ 7, 8주차 코드리뷰 질문
- MyRecruitList에서 ‘내가 지원한 공고’ 목록의 버튼 디자인은 API로 전달받는 값에 따라 달라집니다. 이를 반영하기 위해 getStateStyle 함수를 만들었고, 함수의 파라미터로는 API로 전달받는 값인 변수 state를 사용하고, 함수의 반환값은 버튼의 design과 버튼 내부 text로 설정하였습니다.
```tsx
type MyRecruitListProps = {
title: string;
address: string;
salary: string;
image: string;
state: string;
};

type designProps = {
design: 'default' | 'outlined' | 'textbutton' | 'deactivate';
text: string;
};

## 🙋‍♂️ 5주차 코드리뷰 질문
- 하나의 페이지 내에서만 여러번 사용되는 공통 컴포넌트의 경우, components/common 폴더에 공통 컴포넌트로 만들어 취급하는 것이 좋은지, 혹은 해당 페이지 코드 파일이 위치한 폴더에 컴포넌트를 만들거나 해당 페이지 코드 파일 하단에 작성하는 등 colocation 원칙을 적용해서 가까이 위치시키는 것이 좋을지 궁금합니다.
- `Header` 컴포넌트에서 다른 theme을 가진 버튼들에 공통된 스타일을 적용하면서, 특정 버튼에만 추가적인 스타일을 주는 작업을 했습니다. 아래와 같이 각 버튼에 공통적으로 적용될 스타일을 `commonButtonStyles`로 정의하고, `theme=default`인 버튼에만 추가 스타일을 적용해보았는데, 제가 구현한 방식보다 더 괜찮은 방법이 있는지 궁금합니다.
```jsx
const commonButtonStyles = css`
white-space: nowrap;
border-radius: 4px;
`;
function getStateStyle(state: MyRecruitListProps['state']): designProps {
switch (state) {
case '근로계약서 서명하기':
return { design: 'default', text: '근로계약서 서명하기' };
case '채용 마감':
return { design: 'deactivate', text: '채용 마감' };
case '지원서 검토중':
return { design: 'outlined', text: '지원서 검토중' };
case '채용 완료':
return { design: 'deactivate', text: '채용 완료' };
default:
return { design: 'deactivate', text: '알 수 없음' }; // 상태가 정의되지 않은 경우
}
}
```

그런데 desginProps의 design은 공통컴포넌트로 만들었던 button의 design 프롭스로 전달되는 값으로, 이미 button 컴포넌트 내부에서 design 도메인을 정의한 바가 있습니다. 만약 위 코드처럼 제가 작성한대로 한다면, button 컴포넌트에서 design부분을 수정한다면 위 코드도 직접 수정해야하는 불편함이 있을것 같다고 생각합니다. 혹시 공통컴포넌트에서 정의한 프롭스 도메인을 다른 부분에서 재사용할 수 있는 좋은 방법이 있을까요?
getStateStyle의 인자로 받는 state는 MyRecruitListProps의 state와 같은 값을 공유하므로 위와 같이 작성했는데, 혹시 미리 정의한 프롭스 타입의 일부분을 사용하는 더 좋은 방법이 있을까요?
아래와 같이 props 타입을 지정하고, 아래 타입을 가진 데이터를 mock데이터로 받아오는 코드를 작성했습니다.

```tsx
export type StateProps = '근로계약서 서명하기' | '채용 마감' | '지원서 검토중' | '채용 완료';
<MyRecruitList myRecruitList={myRecruitList} />
```
<Button theme="outlined" css={[commonButtonStyles]}>
채용공고 등록
</Button>
<Button theme="textbutton" css={[commonButtonStyles]}>
닉네임
</Button>
<Button
css={[
commonButtonStyles,
css`
background-color: #0a65cc;
color: #fff;
`,
]}
>
로그아웃
</Button>
위 코드에서 {myRecruitList}가 mock데이터인데, 이때 이 데이터가 타입에 맞지 않는다고, 특히 state 부분이 StateProps 값을 기대하는 반면 string 값이 들어온다고 타입오류가 발생합니다. 그래서 mock데이터 자체에 아래와 같이 타입을 지정하니 오류는 사라졌는데, 이부분에 진짜 API 데이터를 연결하게 되어도 안전할지 모르겠습니다.
```tsx
export const myRecruitList: MyRecruitListProps[] = [
{
id: 1,
image:
'https://img.freepik.com/free-photo/low-angle-view-of-skyscrapers_1359-1105.jpg?size=626&ext=jpg&ga=GA1.1.1297763733.1727740800&semt=ais_hybrid',
title: '제목',
area: '대전광역시 유성구',
state: '근로계약서 서명하기',
},
...
```
- 태블릿(`768px`)과 모바일(`480px`)에서 반응형을 고려하여 `breakpoints`를 정의하였고, 이를 보다 명시적으로 활용하기 위해 `responsiveStyles` 함수를 구현했습니다.
멘토님께서는 보통 반응형 스타일링을 구현할 때 어떤 방식을 사용하시나요?
혹시 제가 사용한 `responsiveStyles` 함수보다 효율적이거나 코드의 가독성을 높일 수 있는 더 나은 방법이 있을까요? 멘토님이 추천하는 방법이나 일반적으로 사용되는 best practice 또한 궁금합니다.
- 현재 `Modal` 컴포넌트를 사용할 때마다 `useToggle` 커스텀 훅을 함께 사용해야 해서, 모달을 제어하기 위한 코드가 흩어져 있는 느낌입니다. 이렇게 되면 모달 관련 로직으로 인해서 단일 책임 원칙에 어긋난다는 생각이 들곤 합니다.
보다 나은 방식으로 `Modal` 컴포넌트를 동작시킬 수 있는 방법이 있을까요? `useToggle`처럼 모달을 제어하는 로직을 간소화하고, 모달 컴포넌트 자체가 스스로 상태를 관리하거나 쉽게 제어 가능한 형태로 구현할 수 있는지 궁금합니다.
## 🙋‍♂️ 4주차 코드리뷰 질문
- `Modal` 컴포넌트를 구현하면서 텍스트 부분과 버튼 부분에 들어갈 내용은 개발할 때 코드를 작성하는 사람이 자유롭게 작성하여 구성할 수 있도록 `textChildren``buttonChildren`만으로 구성하였는데, 더 적합하거나 지향하는 방식이 있을까요
이처럼, 여러 값만 도메인으로 설정한 타입들에 한해 자주 에러가 발생하는데 이런 부분을 어떻게 해결하면 좋을까요?
- 고용주 마이페이지, 내 회사 페이지, 지원자 목록 페이지에 공통으로 사용되는 테이블 컴포넌트를 구현했습니다. 세 페이지 모두 같은 스타일의 테이블을 사용하여 구현 시 스타일을 컴포넌트 자체에 내장하여 일괄적으로 적용되도록 했는데, 이런 방식으로 사용해도 될지 궁금합니다. 스타일을 고정하여 일관성을 유지하는 것과 사용 시점에 스타일을 적용해서 유연성을 제공하는 방법 중에 어느 것을 선택하는 것이 좋을까요?
116 changes: 111 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@
"@loadable/component": "^5.16.4",
"@react-oauth/google": "^0.12.1",
"@tanstack/react-query": "^5.56.2",
"@types/react-signature-canvas": "^1.0.6",
"axios": "^1.7.7",
"buffer": "^6.0.3",
"csstype": "^3.1.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.1.2",
"react-hook-form": "^7.53.0",
"react-router-dom": "^6.26.2",
"react-signature-canvas": "^1.0.6",
"zustand": "^4.5.5"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions src/apis/apiPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const APIPath = {
signEmployeeContract: '/api/contract',
makeEmployerContract: '/api/categories',
downloadContract: '/api/contract/:applyId/download',
registerSign: '/api/sign',
};

export const getDynamicAPIPath = {
Expand Down
22 changes: 22 additions & 0 deletions src/apis/registerSign/registerSign.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { http, HttpResponse } from 'msw';
import { getRegisterSign } from './useRegisterSign';

export const registerSignMockHandler = [
http.post(getRegisterSign(), async ({ request }) => {
const formData = await request.formData();
const file = formData.get('imageUrl');

if (file instanceof File) {
console.log('File name:', file.name);
console.log('File type:', file.type);
console.log('File size:', file.size);
} else {
console.log('오류 오류');
}

return HttpResponse.json(
{ message: '파일이 성공적으로 수신되었습니다.', fileName: file instanceof File ? file.name : null },
{ status: 201 },
);
}),
];
27 changes: 27 additions & 0 deletions src/apis/registerSign/useRegisterSign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { APIPath } from '@/apis/apiPath';
import { clientInstance } from '@/apis/instance';
import { useMutation } from '@tanstack/react-query';
// import { useNavigate } from 'react-router-dom';

export const getRegisterSign = () => `${APIPath.registerSign}`;

export const RegisterSign = async (req: FormData) => {
const response = await clientInstance.post(getRegisterSign(), req, {
headers: {
'Content-Type': 'multipart/form-data',
},
});
return response.data;
};

export const FetchRegisterSign = () => {
// const nav = useNavigate();

return useMutation({
mutationFn: (req: FormData) => RegisterSign(req),
onSuccess: () => {
// 고용주, 근로자 구분 리다이렉트 추후 구현
// nav('/');
},
});
};
2 changes: 2 additions & 0 deletions src/mocks/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { recruitmentsMockHandler } from '@/apis/home/mocks/recruitmentsMockHandl
import { slidesMockHandler } from '@/apis/home/mocks/slidesMockHandler';
import { EmployeePageMockHandler } from '@/apis/employee/mock/getMyApplication.mock';
import { noticesMockHandler } from '@/apis/employer/mock/postNotice.mock';
import { registerSignMockHandler } from '@/apis/registerSign/registerSign.mock';

export const handlers = [
...recruitmentsMockHandler,
...slidesMockHandler,
...noticesMockHandler,
...EmployeePageMockHandler,
...registerSignMockHandler,
];
Loading

0 comments on commit 21c3b41

Please sign in to comment.