Feat/413 유저/기업 홈 수정#421
Conversation
📝 WalkthroughWalkthrough이번 변경에서는 홈 화면의 유저/기업별 맞춤형 포스트, 커리어, 이력서 리스트 구성을 대폭 개편하고, 각 리스트 컴포넌트의 UI 및 props를 확장했습니다. 또한 커리어, 이력서, 공고 검색 필터 포맷터와 데이터 조회 훅이 역할별로 분리 및 강화되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant HomePostSection
participant API
participant ListComponent
User->>HomePostSection: 홈 진입
HomePostSection->>API: (유저/기업별) 트렌딩/북마크 데이터 요청
API-->>HomePostSection: 데이터 반환
HomePostSection->>ListComponent: 데이터 전달 (props)
ListComponent-->>User: 리스트 렌더링 (타이틀/See More/카드 등)
User->>ListComponent: See More 클릭
ListComponent->>HomePostSection: onSeeMoreClick 콜백
HomePostSection->>User: 검색/스크랩 페이지로 이동
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 없음) Possibly related PRs
Suggested reviewers
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: .coderabbit.yml 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 5
🔭 Outside diff range comments (1)
src/pages/PostSearch/PostSearchPage.tsx (1)
55-59: Performance API 브라우저 호환성 확인 필요Performance Navigation Timing API가 모든 브라우저에서 지원되지 않을 수 있습니다. 특히 오래된 브라우저에서는 오류가 발생할 수 있습니다.
-const isReload = - ( - performance.getEntriesByType( - 'navigation', - )[0] as PerformanceNavigationTiming - )?.type === 'reload'; +let isReload = false; +try { + const navEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; + isReload = navEntry?.type === 'reload'; +} catch (error) { + // Fallback for browsers that don't support Performance API + console.warn('Performance API not supported'); +}
🧹 Nitpick comments (12)
src/hooks/api/useCareer.ts (1)
203-203: 주석의 번호를 확인해주세요.주석에 "144"라고 되어있는데, 다른 API 주석들과의 일관성을 위해 "14.4"가 맞는지 확인해주세요.
-// 144 (게스트) 커리어 리스트 일반 조회 훅 (페이징 없이 한번에 호출) +// 14.4 (게스트) 커리어 리스트 일반 조회 훅 (페이징 없이 한번에 호출)src/components/Home/HomeCareerPostingList.tsx (2)
51-53: 수평 스크롤 컨테이너의 키보드 접근성 개선 필요현재 구현은 마우스/터치 스크롤만 지원합니다. 키보드 사용자를 위해 좌우 화살표 키로 스크롤할 수 있도록 구현을 추가하는 것이 좋겠습니다.
-<div className="flex overflow-x-scroll whitespace-nowrap no-scrollbar"> +<div + className="flex overflow-x-scroll whitespace-nowrap no-scrollbar" + tabIndex={0} + onKeyDown={(e) => { + const scrollAmount = 200; + if (e.key === 'ArrowLeft') { + e.currentTarget.scrollBy({ left: -scrollAmount, behavior: 'smooth' }); + } else if (e.key === 'ArrowRight') { + e.currentTarget.scrollBy({ left: scrollAmount, behavior: 'smooth' }); + } + }} +>
16-17: 에러 상태 처리 추가 검토현재 로딩과 빈 데이터 상태만 처리하고 있습니다. API 호출 실패 시 에러 상태도 처리하면 더 나은 사용자 경험을 제공할 수 있습니다.
에러 상태 처리를 위한 props 추가와 UI 구현이 필요하신가요?
src/components/Home/HomeCareerPostCard.tsx (1)
33-35: "No Image" 텍스트 다국어 지원 필요"No Image" 텍스트가 영어로 하드코딩되어 있습니다. 다른 UI 요소들과 일관성을 위해 번역 상수를 사용하는 것이 좋겠습니다.
-No Image +{postTranslation.noImage || '이미지 없음'}src/components/PostSearch/CareerSearchSection.tsx (1)
44-91: 에러 상태 처리 추가 권장현재 로딩 상태만 처리하고 있으나, API 호출 실패 시 에러 상태도 처리하면 사용자 경험이 개선될 것입니다.
useInfiniteGetCareerList와useInfiniteGetCareerGuestList훅에서error상태도 반환받아 처리하는 것을 고려해보세요.src/pages/PostSearch/PostSearchPage.tsx (1)
18-25: state 객체 타입 검증 추가 권장location.state의 타입이 보장되지 않으므로, 런타임 타입 검증을 추가하면 더 안전할 것 같습니다.
타입 가드 함수를 만들어 state 객체의 속성들을 검증하는 것을 고려해보세요. 예를 들어:
const isValidSearchState = (state: any): state is SearchState => { return state && (typeof state.searchText === 'string' || state.searchText === undefined) && // ... 다른 필드들 검증 };src/components/Home/HomeJobPostingSection.tsx (1)
62-89: 네비게이션 state 객체 상수화 권장네비게이션 함수들이 호출될 때마다 새로운 state 객체가 생성됩니다. 이를 상수로 정의하면 메모리 효율성이 개선됩니다.
+const NAVIGATION_STATES = { + JOB_SEARCH: { + initialMenu: POST_SEARCH_PAGE_MENU.POST, + postSortType: POST_SORTING.POPULAR, + }, + CAREER_SEARCH: { + initialMenu: POST_SEARCH_PAGE_MENU.CAREER, + careerSortType: POST_SORTING.POPULAR, + }, + RESUME_SEARCH: { + initialMenu: POST_SEARCH_PAGE_MENU.CAREER, + resumeSortType: POST_SORTING.POPULAR, + }, +}; const goToJobSearch = () => { - navigate('/search', { - state: { - initialMenu: POST_SEARCH_PAGE_MENU.POST, - postSortType: POST_SORTING.POPULAR, - }, - }); + navigate('/search', { state: NAVIGATION_STATES.JOB_SEARCH }); };src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx (3)
32-48: 이미지 폴백 디자인을 개선해보세요.현재 "No Image" 텍스트가 하드코딩되어 있고, 그라디언트 배경색이 다소 강해 보입니다. 브랜드 일관성을 위해 다른 컴포넌트와 유사한 폴백 스타일을 사용하는 것이 좋겠습니다.
다음과 같이 개선할 수 있습니다:
- <div className="flex items-center justify-center w-full h-full text-white bg-gradient-to-r from-purple-500 to-pink-500"> - No Image + <div className="flex items-center justify-center w-full h-full text-text-alternative bg-surface-secondary"> + <EmptyJobIcon className="w-8 h-8" /> </div>
55-57: 하드코딩된 폴백 텍스트를 상수로 분리하세요.title이 없을 때 표시되는 텍스트가 하드코딩되어 있습니다. 유지보수성을 위해 상수나 번역 파일로 분리하는 것이 좋겠습니다.
- {cardData?.title || '친절한 서비스를 고객을 맞게 만들어보아요!'} + {cardData?.title || postTranslation.defaultEmployeeTitle.ko}
74-74: 비자 정보 변환 로직을 재사용 가능한 유틸로 분리하세요.
replace(/_/g, '-')로직이 여러 곳에서 사용될 것 같습니다. 재사용성을 위해 별도 유틸 함수로 분리하는 것을 고려해보세요.// utils/formatVisa.ts export const formatVisaForDisplay = (visa: string) => visa.replace(/_/g, '-');src/utils/formatSearchFilter.ts (2)
127-127: 카테고리 조인 로직에 일관성을 적용하세요.카테고리 필터링에서
safeJoin유틸을 사용하지 않고 직접join을 사용하고 있습니다. 일관성을 위해safeJoin을 사용하는 것이 좋겠습니다.- category: careerCategory.length > 0 ? careerCategory.join(',') : null, + category: careerCategory.length > 0 ? safeJoin(careerCategory) : null,Also applies to: 166-166
195-204: 이력서 검색 필터에도 safeJoin을 적용하세요.다른 포맷터와 일관성을 위해
safeJoin유틸을 사용하는 것이 좋겠습니다.return { size: 5, sorting: sortType, - visa: - filterList[EMPLOYEE_SEARCH_CATEGORY.VISA] - ?.join(',') - ?.replace(/-/g, '_') ?? null, - korean: filterList[EMPLOYEE_SEARCH_CATEGORY.KOREAN]?.join(',') ?? null, - major: filterList[EMPLOYEE_SEARCH_CATEGORY.MAJOR]?.join(',') ?? null, - nationality: - filterList[EMPLOYEE_SEARCH_CATEGORY.NATIONALITY]?.join(',') ?? null, - industry: filterList[EMPLOYEE_SEARCH_CATEGORY.INDUSTRY]?.join(',') ?? null, + visa: safeJoin(filterList[EMPLOYEE_SEARCH_CATEGORY.VISA])?.replace(/-/g, '_') ?? null, + korean: safeJoin(filterList[EMPLOYEE_SEARCH_CATEGORY.KOREAN]) ?? null, + major: safeJoin(filterList[EMPLOYEE_SEARCH_CATEGORY.MAJOR]) ?? null, + nationality: safeJoin(filterList[EMPLOYEE_SEARCH_CATEGORY.NATIONALITY]) ?? null, + industry: safeJoin(filterList[EMPLOYEE_SEARCH_CATEGORY.INDUSTRY]) ?? null, };
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx(2 hunks)src/components/Home/HomeCareerPostCard.tsx(1 hunks)src/components/Home/HomeCareerPostingList.tsx(1 hunks)src/components/Home/HomeJobPostingSection.tsx(1 hunks)src/components/PostSearch/CareerCardList.tsx(2 hunks)src/components/PostSearch/CareerSearchSection.tsx(4 hunks)src/components/PostSearch/PostSearchFilterList.tsx(2 hunks)src/hooks/api/useCareer.ts(1 hunks)src/pages/PostSearch/PostSearchPage.tsx(4 hunks)src/pages/Resume/ScrappedJobPostsPage.tsx(3 hunks)src/types/api/career.ts(1 hunks)src/utils/formatSearchFilter.ts(1 hunks)
🔇 Additional comments (15)
src/types/api/career.ts (1)
16-16: 타입 확장이 적절합니다.
CareerListItemType에img_urls속성 추가가 잘 되었습니다. 홈 화면의 커리어 카드에서 이미지를 표시하기 위한 기반 작업으로 보이며, 선택적 속성으로 정의되어 기존 코드와의 호환성도 유지됩니다.src/pages/Resume/ScrappedJobPostsPage.tsx (1)
84-88: 네비게이션 state를 활용한 초기 필터 설정이 좋습니다.
useLocation을 사용하여 네비게이션으로부터 필터 탭의 초기값을 받아오는 로직이 잘 구현되었습니다. 사용자 경험 향상에 도움이 될 것 같습니다.src/components/PostSearch/PostSearchFilterList.tsx (1)
46-52: 안전한 배열 접근 패턴이 잘 적용되었습니다.
region1Depth?.map(...) ?? []을 사용하여region1Depth가 undefined일 때의 런타임 에러를 방지했습니다. 방어적 프로그래밍의 좋은 예시입니다.src/components/PostSearch/CareerCardList.tsx (3)
101-105: 컴포넌트 재사용성이 향상되었습니다.
title과onSeeMoreClick선택적 props 추가로 컴포넌트의 재사용성이 크게 향상되었습니다. 다양한 컨텍스트에서 활용할 수 있게 되었네요.Also applies to: 109-113
149-162: 헤더 영역 구현이 깔끔합니다.조건부 렌더링을 통한 제목과 "See More" 버튼 구현이 잘 되어있습니다. UI 일관성도 유지되고 있습니다.
165-165: 안전한 배열 접근이 적용되었습니다.
careerData?.map으로 optional chaining을 적용하여 undefined 에러를 방지했습니다.src/hooks/api/useCareer.ts (2)
190-201: 단순 조회 훅 추가가 좋습니다.무한스크롤이 필요하지 않은 경우를 위한
useGetCareerList훅 추가가 적절합니다. 홈 화면 등에서 제한된 개수의 데이터만 필요할 때 유용할 것 같습니다.
204-214: 게스트용 단순 조회 훅도 잘 구현되었습니다.로그인 여부에 따른 API 분리가 일관되게 적용되었고, 구현도 기존 패턴을 잘 따르고 있습니다.
src/components/PostSearch/CareerSearchSection.tsx (1)
52-58: 하드코딩된 페이지네이션 값 검토 필요
page: 1과size: 5가 하드코딩되어 있습니다. 이는 무한 스크롤 로직과 맞지 않을 수 있으며, 실제 페이지네이션 상태와 동기화되지 않을 수 있습니다.무한 스크롤에서 초기 페이지와 사이즈 설정이 올바른지 확인이 필요합니다.
useInfiniteGetCareerList와useInfiniteGetCareerGuestList훅의 구현을 확인해주세요.Also applies to: 70-77
src/components/Home/HomeJobPostingSection.tsx (1)
99-101: flatMap 사용 시 undefined 처리 개선 필요pages가 undefined일 경우 flatMap이 예상치 못한 동작을 할 수 있습니다.
-resumeData={ - trendingResumeData?.pages?.flatMap((page) => page.data.resumes) ?? - [] -} +resumeData={ + trendingResumeData?.pages + ? trendingResumeData.pages.flatMap((page) => page.data.resumes) + : [] +}Likely an incorrect or invalid review comment.
src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx (1)
26-28: 다음 스크립트로 전체 코드베이스에서/employer/search경로 정의를 다시 검색해 보세요:#!/bin/bash # employer/search 경로가 라우터에 정의되어 있는지 전체 코드베이스에서 검색 rg -g '*.ts*' -A 3 -B 3 "employer/search"src/utils/formatSearchFilter.ts (4)
14-15: 유틸 함수가 잘 구현되었습니다.
safeJoin함수는 null/undefined 안전성을 제공하고 중복 코드를 줄여주는 좋은 접근입니다.
22-44: TypeScript 타입 정의가 명확합니다.입력과 출력 타입을 명확히 분리한 것이 코드의 가독성과 타입 안전성을 크게 향상시켰습니다.
111-130: 함수 분리가 적절합니다.사용자와 게스트용 커리어 검색 필터를 분리한 것이 코드의 명확성과 유지보수성을 향상시켰습니다.
55-65: formatSearchFilter.ts의 전체 로직과 safeJoin 정의를 확인하기 위해 아래 스크립트를 실행해주세요.#!/bin/bash # 1. formatSearchFilter 함수 전체 내용 확인 sed -n '1,200p' src/utils/formatSearchFilter.ts # 2. safeJoin 함수 정의 위치 확인 rg -n "export function safeJoin" -A5 -B5 --glob "*.ts"
| {/* 주관-주최 */} | ||
| <p className="whitespace-normal caption-12-regular text-text-alternative"> | ||
| {careerData.organizer_name ?? '-'} | ||
| <span className="inline-block h-3 mx-1 align-middle border bg-border-alternative"></span> |
There was a problem hiding this comment.
구분선 스타일 수정 필요
현재 구분선에 너비가 지정되지 않아 보이지 않을 수 있습니다.
-<span className="inline-block h-3 mx-1 align-middle border bg-border-alternative"></span>
+<span className="inline-block w-[1px] h-3 mx-1 align-middle bg-border-alternative"></span>🤖 Prompt for AI Agents
In src/components/Home/HomeCareerPostCard.tsx at line 47, the span element used
as a divider lacks a width, causing it to potentially be invisible. Add an
explicit width style or class to the span to ensure the divider is visible, such
as setting a fixed width or using a utility class that defines width.
| <article | ||
| className="flex flex-col gap-2 w-[9.063rem] m-1 rounded-lg" | ||
| onClick={goToCareerDetailPage} | ||
| > |
There was a problem hiding this comment.
클릭 가능한 카드에 대한 시각적 피드백과 접근성 개선 필요
article 요소가 클릭 가능하지만 사용자에게 이를 알려주는 시각적 표시가 없고, 키보드 접근성도 부족합니다.
<article
className="flex flex-col gap-2 w-[9.063rem] m-1 rounded-lg"
+ role="button"
+ tabIndex={0}
+ style={{ cursor: 'pointer' }}
onClick={goToCareerDetailPage}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ goToCareerDetailPage();
+ }
+ }}
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <article | |
| className="flex flex-col gap-2 w-[9.063rem] m-1 rounded-lg" | |
| onClick={goToCareerDetailPage} | |
| > | |
| <article | |
| className="flex flex-col gap-2 w-[9.063rem] m-1 rounded-lg" | |
| role="button" | |
| tabIndex={0} | |
| style={{ cursor: 'pointer' }} | |
| onClick={goToCareerDetailPage} | |
| onKeyDown={(e) => { | |
| if (e.key === 'Enter' || e.key === ' ') { | |
| e.preventDefault(); | |
| goToCareerDetailPage(); | |
| } | |
| }} | |
| > |
🤖 Prompt for AI Agents
In src/components/Home/HomeCareerPostCard.tsx around lines 20 to 23, the
clickable article element lacks visual feedback and keyboard accessibility. Add
appropriate focus styles and role="button" to the article element, and handle
keyboard events like onKeyDown to support keyboard navigation. This will improve
both visual indication and accessibility for keyboard users.
| const { data: trendingResumeData, isLoading: trendingResumeLoading } = | ||
| useInfiniteGetEmployeeResumeList( | ||
| { | ||
| size: 5, | ||
| sorting: POST_SORTING.POPULAR, | ||
| }, | ||
| isOwner, | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
조건부 데이터 페칭 최적화 필요
현재 구현에서는 조건에 관계없이 모든 훅이 호출되고 있어, 불필요한 API 요청이 발생할 수 있습니다. 특히 고용주 계정에서는 커리어 관련 API가, 일반 유저에서는 이력서 API가 호출될 필요가 없습니다.
각 훅의 enabled 조건을 더 명확히 하여 필요한 경우에만 API를 호출하도록 개선하는 것이 좋겠습니다.
Also applies to: 49-51, 58-59
🤖 Prompt for AI Agents
In src/components/Home/HomeJobPostingSection.tsx around lines 33 to 40, 49 to
51, and 58 to 59, the data fetching hooks are called unconditionally, causing
unnecessary API requests for certain user types. To fix this, add an `enabled`
option to each hook's configuration that checks the user type (e.g., isOwner)
and only enables the hook when the data is needed for that user. This will
prevent irrelevant API calls and optimize data fetching.
| <div className="flex flex-col gap-2"> | ||
| {title && ( | ||
| <div className="flex items-center justify-between py-1"> | ||
| <h3 className="text-black heading-18-semibold">{title}</h3> | ||
| {onSeeMoreClick && ( | ||
| <button | ||
| className="caption-12-regular text-[#9397A1]" | ||
| onClick={onSeeMoreClick} | ||
| > | ||
| See More | ||
| </button> | ||
| )} | ||
| </div> | ||
| )} | ||
|
|
||
| <div className="flex overflow-x-scroll whitespace-nowrap no-scrollbar"> | ||
| {resumeData.map((value) => ( | ||
| <EmployerEmployeeCard key={value.id} cardData={value} /> | ||
| ))} | ||
| </main> | ||
| </div> | ||
|
|
||
| {isLoading && <LoadingItem />} | ||
| </> | ||
| </div> | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
접근성 및 키보드 네비게이션을 개선하세요.
"See More" 버튼에 적절한 aria-label이 없고, 가로 스크롤 컨테이너에 키보드 네비게이션 지원이 부족합니다.
다음과 같이 접근성을 개선할 수 있습니다:
{onSeeMoreClick && (
<button
- className="caption-12-regular text-[#9397A1]"
+ className="caption-12-regular text-[#9397A1]"
+ aria-label={`${title} 목록 더보기`}
onClick={onSeeMoreClick}
>
See More
</button>
)}
- <div className="flex overflow-x-scroll whitespace-nowrap no-scrollbar">
+ <div
+ className="flex overflow-x-scroll whitespace-nowrap no-scrollbar"
+ role="list"
+ aria-label={title ? `${title} 목록` : '직원 카드 목록'}
+ tabIndex={0}
+ >📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="flex flex-col gap-2"> | |
| {title && ( | |
| <div className="flex items-center justify-between py-1"> | |
| <h3 className="text-black heading-18-semibold">{title}</h3> | |
| {onSeeMoreClick && ( | |
| <button | |
| className="caption-12-regular text-[#9397A1]" | |
| onClick={onSeeMoreClick} | |
| > | |
| See More | |
| </button> | |
| )} | |
| </div> | |
| )} | |
| <div className="flex overflow-x-scroll whitespace-nowrap no-scrollbar"> | |
| {resumeData.map((value) => ( | |
| <EmployerEmployeeCard key={value.id} cardData={value} /> | |
| ))} | |
| </main> | |
| </div> | |
| {isLoading && <LoadingItem />} | |
| </> | |
| </div> | |
| ); | |
| <div className="flex flex-col gap-2"> | |
| {title && ( | |
| <div className="flex items-center justify-between py-1"> | |
| <h3 className="text-black heading-18-semibold">{title}</h3> | |
| {onSeeMoreClick && ( | |
| <button | |
| className="caption-12-regular text-[#9397A1]" | |
| aria-label={`${title} 목록 더보기`} | |
| onClick={onSeeMoreClick} | |
| > | |
| See More | |
| </button> | |
| )} | |
| </div> | |
| )} | |
| <div | |
| className="flex overflow-x-scroll whitespace-nowrap no-scrollbar" | |
| role="list" | |
| aria-label={title ? `${title} 목록` : '직원 카드 목록'} | |
| tabIndex={0} | |
| > | |
| {resumeData.map((value) => ( | |
| <EmployerEmployeeCard key={value.id} cardData={value} /> | |
| ))} | |
| </div> | |
| {isLoading && <LoadingItem />} | |
| </div> |
🤖 Prompt for AI Agents
In src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx between
lines 139 and 162, the "See More" button lacks an appropriate aria-label and the
horizontal scroll container does not support keyboard navigation. To fix this,
add a descriptive aria-label to the "See More" button that clearly indicates its
purpose. Also, enhance the scroll container by adding keyboard navigation
support, such as making it focusable with tabIndex and enabling arrow key
scrolling or similar keyboard interactions for better accessibility.
naarang
left a comment
There was a problem hiding this comment.
변경사항이 많았는데 고생많으셨습니다🙇♀️
올려주신 Gif 파일에서 기업회원의 경우에 "see more"와 "더보기"가 혼용되어 나타나는데 "더보기"로 통일되어야 합니다!
- 기존 page 최상위에서 일괄적으로 px-4를 가지고 있었지만 횡스크롤 형 ui들은 px-4에 의해 잘리면 안되므로 해당 ui가 자체적으로 padding을 가지도록 변경함
- 사용자 유형에 따라 퀵메뉴가 존재하지 않음에도 그 container의 padding이 존재, 화면의 불필요한 공백 생성하고 있어 이를 제거함.
- HomePage 내 UI 레이아웃 변경에 따라 인사 문구 등을 출력하는 HomeGreetingSection, 이력서 완성도를 보여주는ResumeHelperBanner, 사용자 유형별 퀵메뉴를 제공하는 HomeMenu를 묶어 HomeTopSection 컴포넌트로 제공 - 기존 광고 배너 캐러셀을 제공했던 HomeBanner를 HomeBannerCarousel로 변경, HomePage에서 단독 호출
- 사용자 유형별로 공고/커리어 카드 내 요구 비자 리스트 문구를 출력하는 로직을 커스텀 훅으로 분리 - 로직 내 삼항 연산자 제거
- 로직과 ui 로직 상 중복되는 부분이 많은 홈/인재 검색에 사용되는 HomeEmployerEmployeeCardList와 EmployerEmployeeCardList에 사용되는 코드를 EmployeeCard로 통합했습니다.
✅ Vitest Test Results📉 Coverage Warnings
🎉 All tests passed!Great job! All 177 tests are working correctly across 22 test files. 🔗 Quick Actions
🤖 Generated by GitHub Actions at 2025-08-01T06:16:34.535Z |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/components/Home/HomeJobPostingSection.tsx (2)
91-91: 섹션 간 간격 확인 필요이전 리뷰에서 기업회원 화면의 "요즘 인기있는 인재"와 "요즘 인기있는 공고" 사이 간격이 피그마보다 크다는 지적이 있었습니다. 현재
gap-8클래스를 사용하고 있는데, 디자인 명세와 일치하는지 확인이 필요합니다.실제 렌더링된 화면에서 섹션 간 간격이 디자인 명세와 일치하는지 확인해 주세요.
80-88: 이력서 검색 네비게이션 경로 수정 필요이전 리뷰 코멘트에서 지적된 문제가 여전히 남아있습니다. 기업회원의 "요즘 인기있는 인재" 더보기 버튼은
/employer/search(인재검색 페이지)로 이동해야 하는데, 현재는/search로 이동하고 있습니다.const goToResumeSearch = () => { - navigate('/search', { - state: { - initialMenu: POST_SEARCH_PAGE_MENU.CAREER, - resumeSortType: POST_SORTING.POPULAR, - }, - }); + navigate('/employer/search'); };
🧹 Nitpick comments (6)
src/components/Home/HomeMenu.tsx (1)
44-44: 메뉴 레이블 단순화가 적절합니다.메뉴 텍스트가 더 간결하고 모던한 형태로 업데이트되었습니다. 모바일 환경에서 더 나은 사용자 경험을 제공할 것으로 예상됩니다.
단축된 레이블이 사용자에게 명확한 의미를 전달하는지 UX 테스트를 고려해보세요.
Also applies to: 49-49, 54-54, 59-59
src/hooks/useEmployeeCard.ts (1)
6-23: 타입 안전성 개선을 고려해보세요.현재
cardData.id의 타입이 명시적으로 검증되지 않습니다. API 응답에서 id가 undefined일 가능성을 고려하여 방어적 코딩을 추가할 수 있습니다.const handleClickBookmark = (e: MouseEvent) => { e.stopPropagation(); + if (!cardData.id) return; mutate(cardData.id); }; const goToResumeDetailPage = () => { + if (!cardData.id) return; navigate(`/employer/search/${cardData.id}`); };src/components/Home/HomeGreetingSection.tsx (1)
25-34: 접근성 개선을 고려해보세요.현재 헤드라인이
h2태그를 사용하고 있는데, 페이지의 전체적인 헤딩 구조를 고려하여 적절한 레벨인지 확인해주세요.SEO와 접근성을 위해 헤딩 레벨이 올바른지 확인하고, 필요시
h1또는 다른 레벨로 조정하는 것을 고려해보세요.src/hooks/useFormattedVisa.ts (1)
10-11: 엣지 케이스 처리를 강화해보세요.현재 빈 배열이나 undefined에 대한 처리는 적절하지만, 배열의 요소가 빈 문자열인 경우에 대한 처리도 고려해볼 수 있습니다.
return useMemo(() => { - if (!visaData || visaData.length === 0) return ''; + if (!visaData || visaData.length === 0) return ''; + const filteredVisaData = visaData.filter(visa => visa && visa.trim()); + if (filteredVisaData.length === 0) return ''; - const visaList = visaData.map((visa) => visa.replace(/_/g, '-')).sort(); + const visaList = filteredVisaData.map((visa) => visa.replace(/_/g, '-')).sort();src/components/Home/HomeEmployeeCardList.tsx (1)
9-9: 타입 이름 불일치 수정 필요
EmployerEmployeeCardListProps타입명이 컴포넌트명HomeEmployeeCardList와 일치하지 않습니다. 일관성을 위해HomeEmployeeCardListProps로 변경하는 것이 좋겠습니다.-type EmployerEmployeeCardListProps = { +type HomeEmployeeCardListProps = {src/components/Common/EmployeeCard/EmployeeCard.tsx (1)
57-57: 비자 포맷팅 로직 중복 제거 필요비자 값에서 언더스코어를 하이픈으로 변경하는 로직이 두 variant에서 중복되고 있습니다. 이를 컴포넌트 상단에서 한 번만 처리하거나 별도 유틸 함수로 추출하는 것이 좋겠습니다.
const EmployeeCard = ({ cardData, variant }: EmployeeCardProps) => { const { handleClickBookmark, goToResumeDetailPage } = useEmployeeCard(cardData); + + const formattedVisa = cardData?.visa?.replace(/_/g, '-'); if (variant === 'horizontal') { return ( // ... 기존 코드 ... <Tag - value={cardData?.visa?.replace(/_/g, '-')} + value={formattedVisa} // ... 나머지 props ... /> // ... 기존 코드 ... ); } // Column 형 카드 return ( // ... 기존 코드 ... <Tag - value={cardData.visa.replace(/_/g, '-')} + value={formattedVisa} // ... 나머지 props ... /> // ... 기존 코드 ... ); };Also applies to: 103-103
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
src/assets/icons/Home/AppliedIcon.svgis excluded by!**/*.svgsrc/assets/icons/Home/ApplyIcon.svgis excluded by!**/*.svgsrc/assets/icons/Home/MatchesIcon.svgis excluded by!**/*.svgsrc/assets/icons/Home/RecommendPostIcon.svgis excluded by!**/*.svgsrc/assets/icons/Home/ResumeIcon.svgis excluded by!**/*.svgsrc/assets/icons/Home/SavedIcon.svgis excluded by!**/*.svgsrc/assets/icons/Home/SavedPostIcon.svgis excluded by!**/*.svg
📒 Files selected for processing (23)
src/components/Common/EmployeeCard/EmployeeCard.tsx(1 hunks)src/components/Common/EmployeeCard/EmployeeCardBookmark.tsx(1 hunks)src/components/Common/EmployeeCard/EmployeeCardEmptyState.tsx(1 hunks)src/components/Common/EmployeeCard/index.ts(1 hunks)src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx(2 hunks)src/components/Home/HomeBannerCarousel.tsx(4 hunks)src/components/Home/HomeCareerPostCard.tsx(1 hunks)src/components/Home/HomeCareerPostingList.tsx(1 hunks)src/components/Home/HomeEmployeeCardList.tsx(1 hunks)src/components/Home/HomeGreetingSection.tsx(1 hunks)src/components/Home/HomeJobPostingList.tsx(1 hunks)src/components/Home/HomeJobPostingSection.tsx(1 hunks)src/components/Home/HomeMenu.tsx(3 hunks)src/components/Home/HomePostCard.tsx(3 hunks)src/components/Home/HomeTopSection.tsx(1 hunks)src/components/PostSearch/CareerCardList.tsx(2 hunks)src/components/PostSearch/CareerSearchSection.tsx(3 hunks)src/constants/translation.ts(1 hunks)src/hooks/useEmployeeCard.ts(1 hunks)src/hooks/useFormattedVisa.ts(1 hunks)src/pages/Home/HomePage.tsx(2 hunks)src/pages/PostSearch/PostSearchPage.tsx(3 hunks)src/pages/Resume/ScrappedJobPostsPage.tsx(3 hunks)
✅ Files skipped from review due to trivial changes (6)
- src/components/Common/EmployeeCard/index.ts
- src/components/Home/HomeJobPostingList.tsx
- src/components/Home/HomeTopSection.tsx
- src/constants/translation.ts
- src/components/Common/EmployeeCard/EmployeeCardEmptyState.tsx
- src/pages/Home/HomePage.tsx
🚧 Files skipped from review as they are similar to previous changes (7)
- src/pages/Resume/ScrappedJobPostsPage.tsx
- src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx
- src/pages/PostSearch/PostSearchPage.tsx
- src/components/PostSearch/CareerSearchSection.tsx
- src/components/PostSearch/CareerCardList.tsx
- src/components/Home/HomeCareerPostingList.tsx
- src/components/Home/HomeCareerPostCard.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/components/Home/HomeGreetingSection.tsx (1)
src/store/user.ts (1)
useUserStore(12-24)
src/components/Home/HomeEmployeeCardList.tsx (2)
src/types/api/resumes.ts (1)
EmployeeResumeListItemType(91-102)src/components/Common/LoadingItem.tsx (1)
LoadingItem(3-9)
🔇 Additional comments (19)
src/components/Home/HomeMenu.tsx (2)
1-4: 아이콘 임포트 변경사항이 적절합니다.새로운 메뉴 레이블과 일치하도록 아이콘 임포트가 업데이트되었습니다. 네이밍 컨벤션이 일관되고 구문적으로 올바릅니다.
73-88: 렌더링 로직과 스타일링 개선이 우수합니다.조건부 렌더링을 통해 메뉴 아이템이 없을 때 빈 내비게이션이 렌더링되지 않도록 방어적 프로그래밍을 적용했습니다. 버튼 레이아웃과 텍스트 스타일링도 더 일관되고 모던하게 업데이트되었습니다.
src/hooks/useEmployeeCard.ts (2)
10-13: 북마크 처리 로직이 올바르게 구현되었습니다.이벤트 전파를 중단하고 뮤테이션을 호출하는 로직이 적절합니다. 클릭 이벤트가 상위 컨테이너로 전파되지 않도록 하여 의도치 않은 네비게이션을 방지합니다.
15-17: 경로 확인 완료:/employer/search/:id라우트가 정의되어 있습니다.
src/router.tsx에서path="/employer/search/:id"로 명시되어 있어,
navigate(\/employer/search/${cardData.id}`)` 호출은 올바른 상세 페이지 이동입니다.따라서 코드 수정은 필요하지 않습니다.
src/components/Home/HomeGreetingSection.tsx (1)
19-23: 헤드라인 메시지 로직이 명확합니다.사용자 타입에 따른 헤드라인 메시지 분기 처리가 적절하게 구현되었습니다. 기업 사용자에게는 한국어, 일반 사용자에게는 영어로 제공하는 것이 합리적입니다.
src/components/Home/HomePostCard.tsx (3)
17-17: 사용자 타입 결정 로직이 명확합니다.
account_type을 기반으로userType문자열을 생성하는 로직이 간단하고 이해하기 쉽습니다.
29-34: 비자 처리 로직 개선이 우수합니다.
useMemo를 사용하여 비자 데이터 처리를 최적화하고,useFormattedVisa훅을 통해 포맷팅 로직을 중앙화한 것이 좋습니다. 이는 코드의 재사용성과 유지보수성을 크게 향상시킵니다.
38-38: UI 레이아웃 검증 필요
HomePostCard는 현재HomeJobPostingList.tsx에서만 사용되고 있습니다.
article컨테이너에서 마진 클래스가 제거되며 카드 간 간격이 달라질 수 있으므로, 실제 UI에서 의도한 간격이 유지되는지 확인해주세요.
- 확인 위치
• src/components/Home/HomeJobPostingList.tsx (각<HomePostCard />렌더링)src/hooks/useFormattedVisa.ts (2)
5-18: 비자 포맷팅 훅이 잘 구현되었습니다.이 훅은 다음과 같은 장점을 가지고 있습니다:
useMemo를 통한 성능 최적화- 빈 데이터에 대한 적절한 처리
- 일관된 문자열 정규화 (underscore → hyphen)
- 다국어 지원을 위한 번역 상수 활용
15-16: 번역 키 확인 완료
visaAdditional번역 키가src/constants/translation.ts에 아래와 같이 정의되어 있어 정상 동작합니다:
- 파일: src/constants/translation.ts
visaAdditional: { ko: ' 외 ', en: ' +', },src/components/Home/HomeBannerCarousel.tsx (4)
15-28: 데이터 패칭 로직이 명확하게 개선되었습니다.게스트와 로그인 사용자에 대한 조건부 데이터 패칭이 잘 구현되었습니다. 각각의 상태에 맞는 훅을 사용하고, 로딩 상태도 적절히 처리하고 있습니다.
29-49: 캐러셀 구현이 견고합니다.Embla 캐러셀을 사용한 구현이 잘 되어 있습니다:
- 자동재생과 드래그 프리 옵션 설정
- 페이지 변경 감지를 위한 적절한 이벤트 핸들링
- useEffect에서 이벤트 정리까지 포함한 완전한 구현
101-117: 스타일링 변경사항을 확인해주세요.배너 이미지에서 둥근 모서리가 제거되고(
rounded-lg클래스 삭제), 컨테이너의 스타일링도 변경되었습니다. 이러한 변경이 전체 디자인 시스템과 일치하는지 확인이 필요합니다.다음을 확인해주세요:
- 디자인 가이드라인과의 일치성
- 다른 배너 컴포넌트들과의 일관성
82-97: 로딩 및 빈 상태 처리가 적절합니다.로딩 상태와 빈 데이터 상태에 대한 UI 처리가 잘 구현되어 있습니다. 다국어 지원을 위한 번역 상수 사용도 적절합니다.
src/components/Common/EmployeeCard/EmployeeCardBookmark.tsx (1)
1-44: 북마크 컴포넌트 구현이 깔끔합니다!컴포넌트가 잘 구조화되어 있고 다음과 같은 장점이 있습니다:
- 두 가지 variant를 명확하게 구분하여 처리
- 적절한 TypeScript 타입 정의와 기본값 설정
- 기존 코드베이스의 북마크 아이콘 패턴과 일관성 유지
- 단일 책임 원칙을 잘 따르고 있음
src/components/Home/HomeEmployeeCardList.tsx (1)
17-63: 컴포넌트 구현이 잘 되어 있습니다!다음과 같은 장점들이 있습니다:
- 로딩, 빈 상태, 데이터 상태를 명확하게 구분하여 처리
- 적절한 로딩 인디케이터 사용 (초기 로딩과 추가 로딩 구분)
- 새로운 공통 컴포넌트들과 깔끔한 통합
- 가로 스크롤 구현이 적절함
src/components/Common/EmployeeCard/EmployeeCard.tsx (1)
11-126: 전반적으로 잘 구현된 컴포넌트입니다!다음과 같은 장점들이 있습니다:
- 두 가지 variant를 명확하게 구분하여 처리
- 커스텀 훅과 공통 컴포넌트들의 적절한 활용
- 북마크 기능의 깔끔한 통합
- 프로필 이미지 fallback 처리가 적절함
- 클릭 핸들러와 네비게이션 로직이 잘 분리됨
src/components/Home/HomeJobPostingSection.tsx (2)
23-58: 데이터 페칭 조건부 최적화가 잘 되었습니다!이전 리뷰에서 지적된 무조건적인 훅 호출 문제가 해결되었습니다:
- 각 훅이 적절한
enabled조건을 가지고 있음- 사용자 타입과 로그인 상태에 따라 필요한 데이터만 요청
- 불필요한 API 호출 방지가 잘 구현됨
92-157: 역할 기반 UI 렌더링이 잘 구현되었습니다!다음과 같은 개선사항들이 잘 반영되었습니다:
- 고용주와 일반 사용자에 대한 명확한 컨텐츠 구분
- 로그인 상태에 따른 북마크 컨텐츠 조건부 렌더링 (게스트 사용자 문제 해결)
- 적절한 컴포넌트 사용으로 깔끔한 구조
| const getGreetingMessage = (accountType?: UserType) => { | ||
| switch (accountType) { | ||
| case UserType.OWNER: | ||
| return `환영해요, ${name.replace(/-/g, ' ')}님!`; | ||
| case UserType.USER: | ||
| return `Welcome, ${name.replace(/-/g, ' ')}`; | ||
| default: | ||
| return 'Welcome!'; | ||
| } | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
코드 중복이 발생했습니다.
getGreetingMessage 함수의 로직이 src/components/Home/HomeBanner.tsx의 기존 구현과 동일합니다. 코드 중복을 피하기 위해 공통 유틸리티 함수로 추출하는 것을 고려해보세요.
공통 유틸리티 함수를 생성하여 중복을 제거할 수 있습니다:
// src/utils/greeting.ts
export const getGreetingMessage = (accountType?: UserType, name: string) => {
switch (accountType) {
case UserType.OWNER:
return `환영해요, ${name.replace(/-/g, ' ')}님!`;
case UserType.USER:
return `Welcome, ${name.replace(/-/g, ' ')}`;
default:
return 'Welcome!';
}
};🤖 Prompt for AI Agents
In src/components/Home/HomeGreetingSection.tsx around lines 8 to 17, the
getGreetingMessage function duplicates logic already implemented in
src/components/Home/HomeBanner.tsx. To fix this, extract getGreetingMessage into
a shared utility file such as src/utils/greeting.ts, passing both accountType
and name as parameters, then import and use this utility function in both
components to eliminate code duplication.
- 커리어 리스트 요청시 기본 카테고리값 설정 - 커리어 api 훅 주석 수정 - 불필요한 store 조작 코드 제거


Related issue 🛠
Work Description ✏️
- 대표 이미지 (representative_img_url)를 backgroundImage로 적용
- 고정 width, height (w-[9.063rem] h-[6.75rem])
- 제목, 회사명, 주소, employment_type/visa 태그 표시
- 기존 Job 카드 디자인과 완전히 동일하게 통일
- careerData.img_urls[0] 이미지 사용 (없을 경우 No Image 박스 노출)
- organizer_name, host_name 표시
- career_category, visa 태그 표시
Uncompleted Tasks 😅
To Reviewers 📢
Summary by CodeRabbit
Summary by CodeRabbit
New Features
개선사항
버그 수정