diff --git a/index.html b/index.html index 281dc297..39eb5065 100644 --- a/index.html +++ b/index.html @@ -7,23 +7,39 @@ name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" /> - - + + + + + + Giggle + + + +
diff --git a/src/assets/icons/Home/AppliedIcon.svg b/src/assets/icons/Home/AppliedIcon.svg new file mode 100644 index 00000000..dc3dbca1 --- /dev/null +++ b/src/assets/icons/Home/AppliedIcon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/icons/Home/ApplyIcon.svg b/src/assets/icons/Home/ApplyIcon.svg deleted file mode 100644 index 8261ab35..00000000 --- a/src/assets/icons/Home/ApplyIcon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/assets/icons/Home/MatchesIcon.svg b/src/assets/icons/Home/MatchesIcon.svg new file mode 100644 index 00000000..fa628d75 --- /dev/null +++ b/src/assets/icons/Home/MatchesIcon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/icons/Home/RecommendPostIcon.svg b/src/assets/icons/Home/RecommendPostIcon.svg deleted file mode 100644 index c883eb60..00000000 --- a/src/assets/icons/Home/RecommendPostIcon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/assets/icons/Home/ResumeIcon.svg b/src/assets/icons/Home/ResumeIcon.svg index 47d3eade..665fa1ab 100644 --- a/src/assets/icons/Home/ResumeIcon.svg +++ b/src/assets/icons/Home/ResumeIcon.svg @@ -1,12 +1,16 @@ - + + + - - - - - - - - + + + + + + + + + + diff --git a/src/assets/icons/Home/SavedIcon.svg b/src/assets/icons/Home/SavedIcon.svg new file mode 100644 index 00000000..07aff93c --- /dev/null +++ b/src/assets/icons/Home/SavedIcon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/icons/Home/SavedPostIcon.svg b/src/assets/icons/Home/SavedPostIcon.svg deleted file mode 100644 index fe3df148..00000000 --- a/src/assets/icons/Home/SavedPostIcon.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/components/Common/EmployeeCard/EmployeeCard.tsx b/src/components/Common/EmployeeCard/EmployeeCard.tsx new file mode 100644 index 00000000..27db200a --- /dev/null +++ b/src/components/Common/EmployeeCard/EmployeeCard.tsx @@ -0,0 +1,126 @@ +import Tag from '@/components/Common/Tag'; +import { EmployeeResumeListItemType } from '@/types/api/resumes'; +import { useEmployeeCard } from '@/hooks/useEmployeeCard'; +import EmployeeCardBookmark from '@/components/Common/EmployeeCard/EmployeeCardBookmark'; + +type EmployeeCardProps = { + cardData: EmployeeResumeListItemType; + variant: 'horizontal' | 'vertical'; +}; + +const EmployeeCard = ({ cardData, variant }: EmployeeCardProps) => { + const { handleClickBookmark, goToResumeDetailPage } = + useEmployeeCard(cardData); + + if (variant === 'horizontal') { + return ( +
+
+
+ profile image +
+
+
+

+ {cardData?.name}{' '} + + {cardData?.nationality} + +

+ +
+

+ {cardData?.address} +

+
+
+

{cardData?.title}

+
+ + +
+ + ); + } + + // Column 형 카드 + return ( +
+
+ {cardData?.profile_img_url ? ( + profile + ) : ( +
+ No Image +
+ )} +
+ +
+

+ {cardData?.name} +

+ +

+ {cardData?.title || '친절한 서비스를 고객을 맞게 만들어보아요!'} +

+ +
+
+ {cardData?.visa && ( + + )} +
+ + +
+
+
+ ); +}; + +export default EmployeeCard; diff --git a/src/components/Common/EmployeeCard/EmployeeCardBookmark.tsx b/src/components/Common/EmployeeCard/EmployeeCardBookmark.tsx new file mode 100644 index 00000000..3c5de45d --- /dev/null +++ b/src/components/Common/EmployeeCard/EmployeeCardBookmark.tsx @@ -0,0 +1,43 @@ +import BookmarkIcon from '@/assets/icons/BookmarkIcon.svg?react'; +import BookmarkCheckedIcon from '@/assets/icons/BookmarkCheckedIcon.svg?react'; +import { MouseEvent } from 'react'; + +type EmployeeCardBookmarkProps = { + isBookmarked: boolean; + bookmarkCount?: number; + onBookmarkClick: (e: MouseEvent) => void; + variant?: 'icon-only' | 'with-count'; + className?: string; +}; + +const EmployeeCardBookmark = ({ + isBookmarked, + bookmarkCount, + onBookmarkClick, + variant = 'icon-only', + className = '', +}: EmployeeCardBookmarkProps) => { + if (variant === 'with-count') { + return ( +
+ + {bookmarkCount ?? 0} +
+ ); + } + + return ( + + ); +}; + +export default EmployeeCardBookmark; diff --git a/src/components/Common/EmployeeCard/EmployeeCardEmptyState.tsx b/src/components/Common/EmployeeCard/EmployeeCardEmptyState.tsx new file mode 100644 index 00000000..69a2f2fc --- /dev/null +++ b/src/components/Common/EmployeeCard/EmployeeCardEmptyState.tsx @@ -0,0 +1,18 @@ +import EmptyJobIcon from '@/assets/icons/EmptyJobIcon.svg?react'; +import { postTranslation } from '@/constants/translation'; + +const EmployeeCardEmptyState = () => { + return ( +
+ +

+ 찾고 계신 인재가 없어요. +

+

+ {postTranslation.emptySearchResultContent.ko} +

+
+ ); +}; + +export default EmployeeCardEmptyState; diff --git a/src/components/Common/EmployeeCard/index.ts b/src/components/Common/EmployeeCard/index.ts new file mode 100644 index 00000000..4ec1f965 --- /dev/null +++ b/src/components/Common/EmployeeCard/index.ts @@ -0,0 +1,3 @@ +export { default as EmployeeCard } from '@/components/Common/EmployeeCard/EmployeeCard'; +export { default as EmployeeCardBookmark } from '@/components/Common/EmployeeCard/EmployeeCardBookmark'; +export { default as EmployeeCardEmptyState } from '@/components/Common/EmployeeCard/EmployeeCardEmptyState'; diff --git a/src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx b/src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx index 15e39b05..c96c1268 100644 --- a/src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx +++ b/src/components/Employer/EmployeeSearch/EmployerEmployeeCardList.tsx @@ -1,91 +1,10 @@ -import Tag from '@/components/Common/Tag'; -import { useNavigate } from 'react-router-dom'; -import EmptyJobIcon from '@/assets/icons/EmptyJobIcon.svg?react'; -import BookmarkIcon from '@/assets/icons/BookmarkIcon.svg?react'; -import BookmarkCheckedIcon from '@/assets/icons/BookmarkCheckedIcon.svg?react'; import LoadingPostItem from '@/components/Common/LoadingPostItem'; -import { postTranslation } from '@/constants/translation'; import { EmployeeResumeListItemType } from '@/types/api/resumes'; import { LoadingItem } from '@/components/Common/LoadingItem'; -import { MouseEvent } from 'react'; -import { usePutScrapResume } from '@/hooks/api/useResume'; - -const EmployerEmployeeCard = ({ - cardData, -}: { - cardData: EmployeeResumeListItemType; -}) => { - const navigate = useNavigate(); - - const { mutate } = usePutScrapResume(); - - const handleClickBookmark = (e: MouseEvent) => { - e.stopPropagation(); - mutate(cardData.id); - }; - - const goToResumeDetailPage = (id: string) => { - navigate(`/employer/search/${id}`); - }; - - return ( -
goToResumeDetailPage(cardData.id)} - > -
-
- profile image -
-
-
-

- {cardData?.name}{' '} - - {cardData?.nationality} - -

- -
-

- {cardData?.address} -

-
-
-

{cardData?.title}

-
- - -
-
- ); -}; +import { + EmployeeCard, + EmployeeCardEmptyState, +} from '@/components/Common/EmployeeCard'; type EmployerEmployeeCardListProps = { resumeData: EmployeeResumeListItemType[]; @@ -107,24 +26,14 @@ const EmployerEmployeeCardList = ({ } if (resumeData?.length === 0) { - return ( -
- -

- 찾고 계신 인재가 없어요. -

-

- {postTranslation.emptySearchResultContent.ko} -

-
- ); + return ; } return ( <> -
+
{resumeData.map((value: EmployeeResumeListItemType) => ( - + ))}
{isLoading && } diff --git a/src/components/Home/HomeBanner.tsx b/src/components/Home/HomeBannerCarousel.tsx similarity index 62% rename from src/components/Home/HomeBanner.tsx rename to src/components/Home/HomeBannerCarousel.tsx index 928f4d8f..9483f220 100644 --- a/src/components/Home/HomeBanner.tsx +++ b/src/components/Home/HomeBannerCarousel.tsx @@ -1,4 +1,3 @@ -import { UserType } from '@/constants/user'; import { useUserStore } from '@/store/user'; import useEmblaCarousel from 'embla-carousel-react'; import Autoplay from 'embla-carousel-autoplay'; @@ -12,7 +11,54 @@ import { BannerListType } from '@/types/api/banner'; import { isEmployerByAccountType } from '@/utils/signup'; import { bannerTranslation } from '@/constants/translation'; import { useNavigate } from 'react-router-dom'; -import ResumeHelperBanner from '@/components/ManageResume/ResumeHelperBanner'; + +// 배너 캐러셀을 담당하는 컴포넌트 +const HomeBannerCarousel = () => { + const { account_type } = useUserStore(); + const isGuest = account_type === undefined; + const isUser = account_type !== undefined; + + const { data: guestData, isLoading: guestLoading } = + useGetGuestBannerOverview(isGuest); + const { data: userData, isLoading: userLoading } = + useGetUserBannerOverview(isUser); + + const bannerData = isUser ? userData : guestData; + const isLoading = isUser ? userLoading : guestLoading; + + const [emblaRef, embla] = useEmblaCarousel({ dragFree: true, loop: false }, [ + Autoplay({ delay: 3000, stopOnInteraction: false }), + ]); + + const [currentIndex, setCurrentIndex] = useState(0); + + // 페이지 변경 감지 + const onSelect = useCallback(() => { + if (!embla) return; + setCurrentIndex(embla.selectedScrollSnap()); + }, [embla]); + + useEffect(() => { + if (!embla) return; + embla.on('select', onSelect); + onSelect(); + + return () => { + embla.off('select', onSelect); // 이벤트 해제 + }; + }, [embla, onSelect]); + + return ( +
+ +
+ ); +}; const RenderBannerList = ({ data, @@ -33,14 +79,14 @@ const RenderBannerList = ({ if (isLoading) return ( -
+
); if (!data?.length) return ( -
+

{bannerTranslation.emptyTitle[language]}

@@ -52,13 +98,13 @@ const RenderBannerList = ({ return ( <> -
+
{data.map((value: BannerListType) => ( banner image handleClickBannerDetail(value.id)} /> ))} @@ -70,75 +116,4 @@ const RenderBannerList = ({ ); }; -const HomeBanner = () => { - const { account_type, name } = useUserStore(); - const isGuest = account_type === undefined; - const isUser = account_type !== undefined; - - const { data: guestData, isLoading: guestLoading } = - useGetGuestBannerOverview(isGuest); - const { data: userData, isLoading: userLoading } = - useGetUserBannerOverview(isUser); - - const bannerData = isUser ? userData : guestData; - const isLoading = isUser ? userLoading : guestLoading; - - const [emblaRef, embla] = useEmblaCarousel({ dragFree: true, loop: false }, [ - Autoplay({ delay: 3000, stopOnInteraction: false }), - ]); - - const [currentIndex, setCurrentIndex] = useState(0); - - const getGreetingMessage = (accountType?: UserType) => { - switch (accountType) { - case UserType.OWNER: - return `환영해요, ${name.replace(/-/g, ' ')}님!`; - case UserType.USER: - return `Welcome, ${name.replace(/-/g, ' ')}`; - default: - return 'Welcome!'; - } - }; - - // 페이지 변경 감지 - const onSelect = useCallback(() => { - if (!embla) return; - setCurrentIndex(embla.selectedScrollSnap()); - }, [embla]); - - useEffect(() => { - if (!embla) return; - embla.on('select', onSelect); - onSelect(); - - return () => { - embla.off('select', onSelect); // 이벤트 해제 - }; - }, [embla, onSelect]); - - return ( -
-
-

- {getGreetingMessage(account_type)} -

-

- {account_type === UserType.OWNER - ? '필요한 인재를 필요한 순간에 🤝' - : 'Find your perfect job'} -

-
- -
- -
-
- ); -}; - -export default HomeBanner; +export default HomeBannerCarousel; diff --git a/src/components/Home/HomeCareerPostCard.tsx b/src/components/Home/HomeCareerPostCard.tsx new file mode 100644 index 00000000..2109eeff --- /dev/null +++ b/src/components/Home/HomeCareerPostCard.tsx @@ -0,0 +1,77 @@ +import { useNavigate } from 'react-router-dom'; +import { CareerListItemType } from '@/types/api/career'; +import { CAREER_CATEGORY } from '@/constants/postSearch'; +import { useMemo } from 'react'; +import { useUserStore } from '@/store/user'; +import { UserType } from '@/constants/user'; +import { useFormattedVisa } from '@/hooks/useFormattedVisa'; + +type HomeCareerPostCardProps = { + careerData: CareerListItemType; +}; + +const HomeCareerPostCard = ({ careerData }: HomeCareerPostCardProps) => { + const navigate = useNavigate(); + const { account_type } = useUserStore(); + const userType = account_type === UserType.OWNER ? '/employer' : ''; + const goToCareerDetailPage = () => { + navigate(`/career/${careerData.id}`); + }; + + const visaList = useMemo( + () => careerData.visa?.map((visa) => visa.replace(/_/g, '-')).sort(), + [careerData.visa], + ); + const RepresentedVisa = useFormattedVisa(visaList, userType); + + return ( +
+ {/* 이미지 박스 - 기존 Job 공고와 동일하게 통일 */} + {careerData.img_urls && careerData.img_urls.length > 0 ? ( +
+ ) : ( +
+ No Image +
+ )} + +
+ {/* 제목 */} +

+ {careerData.title} +

+ + {/* 주관-주최 */} +

+ {careerData.organizer_name ?? '-'} + + {careerData.host_name ?? '-'} +

+ + {/* 태그 영역 */} +
+ {careerData.career_category && ( + + {CAREER_CATEGORY[careerData.career_category]} + + )} + {careerData.visa && careerData.visa.length > 0 && ( + + {RepresentedVisa} + + )} +
+
+
+ ); +}; + +export default HomeCareerPostCard; diff --git a/src/components/Home/HomeCareerPostingList.tsx b/src/components/Home/HomeCareerPostingList.tsx new file mode 100644 index 00000000..372b78a0 --- /dev/null +++ b/src/components/Home/HomeCareerPostingList.tsx @@ -0,0 +1,58 @@ +import { CareerListItemType } from '@/types/api/career'; +import HomeCareerPostCard from '@/components/Home/HomeCareerPostCard'; +import HomeEmptyJobList from '@/components/Home/HomeEmptyJobList'; +import LoadingPostItem from '@/components/Common/LoadingPostItem'; +import { postTranslation } from '@/constants/translation'; +import { isEmployerByAccountType } from '@/utils/signup'; +import { useUserStore } from '@/store/user'; + +const RenderCareerList = ({ + data, + isLoading, +}: { + data: CareerListItemType[]; + isLoading: boolean; +}) => { + if (isLoading) return ; + if (!data?.length) return ; + return ( + <> + {data.map((career) => ( + + ))} + + ); +}; + +const HomeCareerPostingList = ({ + title, + isLoading, + data, + onSeeMoreClick, +}: { + title: string; + isLoading: boolean; + data: CareerListItemType[]; + onSeeMoreClick: () => void; +}) => { + const { account_type } = useUserStore(); + + return ( +
+
+

{title}

+ +
+
+ +
+
+ ); +}; + +export default HomeCareerPostingList; diff --git a/src/components/Home/HomeEmployeeCardList.tsx b/src/components/Home/HomeEmployeeCardList.tsx new file mode 100644 index 00000000..c0b76ff8 --- /dev/null +++ b/src/components/Home/HomeEmployeeCardList.tsx @@ -0,0 +1,63 @@ +import LoadingPostItem from '@/components/Common/LoadingPostItem'; +import { EmployeeResumeListItemType } from '@/types/api/resumes'; +import { LoadingItem } from '@/components/Common/LoadingItem'; +import { + EmployeeCard, + EmployeeCardEmptyState, +} from '@/components/Common/EmployeeCard'; + +type EmployerEmployeeCardListProps = { + title?: string; + resumeData: EmployeeResumeListItemType[]; + isLoading: boolean; + isInitialLoading: boolean; + onSeeMoreClick?: () => void; +}; + +const HomeEmployeeCardList = ({ + title, + resumeData, + isLoading, + isInitialLoading, + onSeeMoreClick, +}: EmployerEmployeeCardListProps) => { + if (isInitialLoading) { + return ( +
+ +
+ ); + } + + if (!resumeData || resumeData.length === 0) { + return ; + } + + return ( +
+ {title && ( +
+

{title}

+ {onSeeMoreClick && ( + + )} +
+ )} + +
+ {resumeData.map((value) => ( + + ))} +
+ + {isLoading && } +
+ ); +}; + +export default HomeEmployeeCardList; diff --git a/src/components/Home/HomeGreetingSection.tsx b/src/components/Home/HomeGreetingSection.tsx new file mode 100644 index 00000000..a88d2d68 --- /dev/null +++ b/src/components/Home/HomeGreetingSection.tsx @@ -0,0 +1,37 @@ +import { UserType } from '@/constants/user'; +import { useUserStore } from '@/store/user'; + +// 사용자 인사말과 헤드라인을 담당하는 컴포넌트 +const HomeGreetingSection = () => { + const { account_type, name } = useUserStore(); + + const getGreetingMessage = (accountType?: UserType) => { + switch (accountType) { + case UserType.OWNER: + return `환영해요, ${name.replace(/-/g, ' ')}님!`; + case UserType.USER: + return `Welcome, ${name.replace(/-/g, ' ')}`; + default: + return 'Welcome!'; + } + }; + + const getHeadlineMessage = (accountType?: UserType) => { + return accountType === UserType.OWNER + ? '필요한 인재를 필요한 순간에 🤝' + : 'Find your perfect job'; + }; + + return ( + <> +

+ {getGreetingMessage(account_type)} +

+

+ {getHeadlineMessage(account_type)} +

+ + ); +}; + +export default HomeGreetingSection; diff --git a/src/components/Home/HomeJobPostingList.tsx b/src/components/Home/HomeJobPostingList.tsx index 539d582a..2f58c4e6 100644 --- a/src/components/Home/HomeJobPostingList.tsx +++ b/src/components/Home/HomeJobPostingList.tsx @@ -41,16 +41,16 @@ const HomeJobPostingList = ({ return (
-
-

{title}

+
+

{title}

-
+
diff --git a/src/components/Home/HomeJobPostingSection.tsx b/src/components/Home/HomeJobPostingSection.tsx index 40164745..0a11ee66 100644 --- a/src/components/Home/HomeJobPostingSection.tsx +++ b/src/components/Home/HomeJobPostingSection.tsx @@ -1,68 +1,162 @@ import { useNavigate } from 'react-router-dom'; -import { POST_SEARCH_MENU, POST_SORTING } from '@/constants/postSearch'; +import { + POST_SEARCH_MENU, + POST_SEARCH_PAGE_MENU, + POST_SORTING, +} from '@/constants/postSearch'; import { useUserStore } from '@/store/user'; import { UserType } from '@/constants/user'; -import { PostSortingType } from '@/types/PostSearchFilter/PostSearchFilterItem'; import { useHomeJobPosting } from '@/hooks/useHomeJobPosting'; -import HomeJobPostingList from '@/components/Home/HomeJobPostingList'; +import { useInfiniteGetEmployeeResumeList } from '@/hooks/api/useResume'; import { useGetPostList } from '@/hooks/api/usePost'; +import { useGetCareerList } from '@/hooks/api/useCareer'; +import HomeJobPostingList from '@/components/Home/HomeJobPostingList'; +import HomeCareerPostingList from '@/components/Home/HomeCareerPostingList'; +import HomeEmployeeCardList from './HomeEmployeeCardList'; -const HomeJobPostingSection = () => { +const HomePostSection = () => { const navigate = useNavigate(); const { account_type } = useUserStore(); + const isLogin = !!account_type; + const isOwner = account_type === UserType.OWNER; + // 공통: 공고 트렌딩 + const { data: trendingJobData, isLoading: trendingJobLoading } = + useHomeJobPosting(POST_SEARCH_MENU.TRENDING, isLogin); - const { data: trendData, isLoading: trendLoading } = useHomeJobPosting( - POST_SEARCH_MENU.TRENDING, - !!account_type, - ); - const { data: recentlyData, isLoading: recentlyLoading } = useHomeJobPosting( - POST_SEARCH_MENU.RECENTLY, - !!account_type, - ); + // 공통: 공고 북마크 (유저만) + const bookmarkedJobRequest = { size: 5, type: POST_SEARCH_MENU.BOOKMARKED }; + const { data: bookmarkedJobData, isLoading: bookmarkedJobLoading } = + useGetPostList(bookmarkedJobRequest, isLogin); + + // 고용주: 인재 트렌딩 + const { data: trendingResumeData, isLoading: trendingResumeLoading } = + useInfiniteGetEmployeeResumeList( + { + size: 5, + sorting: POST_SORTING.POPULAR, + }, + isOwner, + ); + + // 유저: 커리어 트렌딩 + const trendingCareerRequest = { + size: 5, + sorting: POST_SORTING.POPULAR, + page: 1, + isBookMarked: false, + }; + const { data: trendingCareerData, isLoading: trendingCareerLoading } = + useGetCareerList(trendingCareerRequest, !isOwner && isLogin); - const bookmarkedDataRequest = { + // 유저: 커리어 북마크 + const bookmarkedCareerRequest = { size: 5, - type: POST_SEARCH_MENU.BOOKMARKED, + page: 1, + isBookMarked: true, }; - const { data: userBookmarkedData, isLoading: userBookmarkedLoading } = - useGetPostList(bookmarkedDataRequest, !!account_type); + const { data: bookmarkedCareerData, isLoading: bookmarkedCareerLoading } = + useGetCareerList(bookmarkedCareerRequest, !isOwner && isLogin); - const goToSearchPage = (type: PostSortingType) => { - navigate(`/search`, { state: { sortType: type } }); + // 공고 검색 이동 + const goToJobSearch = () => { + navigate('/search', { + state: { + initialMenu: POST_SEARCH_PAGE_MENU.POST, + postSortType: POST_SORTING.POPULAR, + }, + }); + }; + + // 커리어 검색 이동 + const goToCareerSearch = () => { + navigate('/search', { + state: { + initialMenu: POST_SEARCH_PAGE_MENU.CAREER, + careerSortType: POST_SORTING.POPULAR, + }, + }); + }; + + // 이력서 검색 이동 (고용주용) + const goToResumeSearch = () => { + navigate('/employer/search', { + state: { + initialMenu: POST_SEARCH_PAGE_MENU.CAREER, + resumeSortType: POST_SORTING.POPULAR, + }, + }); }; return ( -
- goToSearchPage(POST_SORTING.POPULAR)} - /> - goToSearchPage(POST_SORTING.RECENT)} - /> - {account_type === UserType.USER && ( - navigate('/resume/scrapped')} - /> +
+ {isOwner ? ( + <> + {/* 고용주: 인재 트렌딩 */} + page.data.resumes) ?? + [] + } + isLoading={trendingResumeLoading} + isInitialLoading={trendingResumeLoading} + onSeeMoreClick={goToResumeSearch} + /> + + {/* 고용주: 공고 트렌딩 */} + + + ) : ( + <> + {/* 일반 유저: 공고 트렌딩 */} + + + {/* 일반 유저: 커리어 트렌딩 */} + + + {/* 일반 유저: 공고 북마크 */} + {isLogin && ( + <> + + navigate('/resume/scrapped', { + state: { filter: 'Job Posting' }, + }) + } + /> + {/* 일반 유저: 커리어 북마크 */} + + navigate('/resume/scrapped', { state: { filter: 'Career' } }) + } + /> + + )} + )}
); }; -export default HomeJobPostingSection; +export default HomePostSection; diff --git a/src/components/Home/HomeMenu.tsx b/src/components/Home/HomeMenu.tsx index 20117fd5..9f848487 100644 --- a/src/components/Home/HomeMenu.tsx +++ b/src/components/Home/HomeMenu.tsx @@ -1,7 +1,7 @@ -import RecommendPostIcon from '@/assets/icons/Home/RecommendPostIcon.svg?react'; -import SavedPostIcon from '@/assets/icons/Home/SavedPostIcon.svg?react'; +import RecommendPostIcon from '@/assets/icons/Home/MatchesIcon.svg?react'; +import SavedPostIcon from '@/assets/icons/Home/SavedIcon.svg?react'; import ResumeIcon from '@/assets/icons/Home/ResumeIcon.svg?react'; -import ApplyIcon from '@/assets/icons/Home/ApplyIcon.svg?react'; +import ApplyIcon from '@/assets/icons/Home/AppliedIcon.svg?react'; import { useNavigate } from 'react-router-dom'; import CommingSoonBottomSheet from '@/components/Home/ComingSoonBottomSheet'; import { useMemo, useState } from 'react'; @@ -41,22 +41,22 @@ const HomeMenu = () => { return [ { icon: , - text: 'Just for You', + text: 'Matches', onClick: () => setIsOpenCommingSoonBottomSheet(true), }, { icon: , - text: 'Saved Jobs', + text: 'Saved', onClick: () => navigate('/resume/scrapped'), }, { icon: , - text: 'My Resume', + text: 'Resume', onClick: () => navigate('/profile/manage-resume'), }, { icon: , - text: 'Applications', + text: 'Applied', onClick: () => navigate('/application'), }, ]; @@ -70,20 +70,22 @@ const HomeMenu = () => { return ( <> - + {menuItems.length > 0 && ( + + )} {isOpenCommingSoonBottomSheet && ( { const { account_type } = useUserStore(); + const userType = account_type === UserType.OWNER ? '/employer' : ''; const { updateCurrentPostId } = useCurrentPostIdStore(); const navigate = useNavigate(); @@ -23,9 +26,16 @@ const HomePostCard = ({ jobPostingData }: HomePostCardProps) => { else navigate(`/post/${jobPostingData.id}`); }; + const visaList = useMemo( + () => + jobPostingData.tags.visa.map((visa) => visa.replace(/_/g, '-')).sort(), + [jobPostingData.tags.visa], + ); + const RepresentedVisa = useFormattedVisa(visaList, userType); + return (
{jobPostingData?.representative_img_url ? ( @@ -65,7 +75,7 @@ const HomePostCard = ({ jobPostingData }: HomePostCardProps) => { fontStyle="caption-12-regular" /> { + return ( +
+ + + +
+ ); +}; + +export default HomeTopSection; \ No newline at end of file diff --git a/src/components/PostSearch/CareerCardList.tsx b/src/components/PostSearch/CareerCardList.tsx index 2cbf5a2b..2c432ff1 100644 --- a/src/components/PostSearch/CareerCardList.tsx +++ b/src/components/PostSearch/CareerCardList.tsx @@ -98,15 +98,19 @@ const CareerCard = ({ careerData }: { careerData: CareerListItemType }) => { }; type CareerCardListProps = { + title?: string; careerData: CareerListItemType[]; isLoading: boolean; isInitialLoading: boolean; + onSeeMoreClick?: () => void; }; const CareerCardList = ({ + title, careerData, isLoading, isInitialLoading, + onSeeMoreClick, }: CareerCardListProps) => { const { account_type } = useUserStore(); @@ -142,6 +146,20 @@ const CareerCardList = ({ return (
+ {/* 여기 title 영역만 추가 */} + {title && ( +
+

{title}

+ {onSeeMoreClick && ( + + )} +
+ )}
{careerData.map((value: CareerListItemType) => ( diff --git a/src/components/PostSearch/CareerSearchSection.tsx b/src/components/PostSearch/CareerSearchSection.tsx index 4cdce772..5354676f 100644 --- a/src/components/PostSearch/CareerSearchSection.tsx +++ b/src/components/PostSearch/CareerSearchSection.tsx @@ -4,7 +4,10 @@ import { useInfiniteScroll } from '@/hooks/useInfiniteScroll'; import { PostSearchType } from '@/hooks/usePostSearch'; import { useUserStore } from '@/store/user'; import { PostSortingType } from '@/types/PostSearchFilter/PostSearchFilterItem'; -import { formatCareerSearchFilter } from '@/utils/formatSearchFilter'; +import { + formatCareerSearchFilterForUser, + formatCareerSearchFilterForGuest, +} from '@/utils/formatSearchFilter'; import { useState } from 'react'; import { postSearchTranslation } from '@/constants/translation'; import { isEmployerByAccountType } from '@/utils/signup'; @@ -36,40 +39,56 @@ const CareerSearchSection = ({ updateSearchOption, }: CareerSearchSectionProps) => { const { account_type } = useUserStore(); + const isLogin = account_type === UserType.USER; const [isLoading, setIsLoading] = useState(false); + // 게스트 요청 const { data: guestCareerData, fetchNextPage: guestFetchNextPage, - hasNextPage: guesthasNextPage, + hasNextPage: guestHasNextPage, isFetchingNextPage: guestIsFetchingNextPage, isLoading: guestIsInitialLoading, } = useInfiniteGetCareerGuestList( - formatCareerSearchFilter(searchOption), - !account_type ? true : false, + formatCareerSearchFilterForGuest({ + page: 1, + size: 5, + searchText: searchOption.searchText, + careerSortType: searchOption.careerSortType, + careerCategory: searchOption.careerCategory, + }), + !isLogin, ); + // 유저 요청 const { data: userCareerData, fetchNextPage: userFetchNextPage, - hasNextPage: userhasNextPage, + hasNextPage: userHasNextPage, isFetchingNextPage: userIsFetchingNextPage, - isLoading: careerIsInitialLoading, + isLoading: userIsInitialLoading, } = useInfiniteGetCareerList( - formatCareerSearchFilter(searchOption), - account_type === UserType.USER ? true : false, + formatCareerSearchFilterForUser({ + page: 1, + size: 5, + searchText: searchOption.searchText, + careerSortType: searchOption.careerSortType, + careerCategory: searchOption.careerCategory, + isBookMarked: false, + }), + isLogin, ); - const data = account_type ? userCareerData : guestCareerData; + const data = isLogin ? userCareerData : guestCareerData; const careerData = data?.pages?.flatMap((page) => page.data.career_list) || []; - const isInitialLoading = account_type - ? careerIsInitialLoading + const isInitialLoading = isLogin + ? userIsInitialLoading : guestIsInitialLoading; - const fetchNextPage = account_type ? userFetchNextPage : guestFetchNextPage; - const hasNextPage = account_type ? userhasNextPage : guesthasNextPage; - const isFetchingNextPage = account_type + const fetchNextPage = isLogin ? userFetchNextPage : guestFetchNextPage; + const hasNextPage = isLogin ? userHasNextPage : guestHasNextPage; + const isFetchingNextPage = isLogin ? userIsFetchingNextPage : guestIsFetchingNextPage; @@ -82,10 +101,8 @@ const CareerSearchSection = ({ const handleUpdateCareerCategory = (category: CareerCategoryKey) => { const categorySet = new Set(searchOption.careerCategory); - if (categorySet.has(category)) categorySet.delete(category); else categorySet.add(category); - updateSearchOption('careerCategory', [...categorySet]); }; @@ -95,10 +112,9 @@ const CareerSearchSection = ({ return ( <> -