-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Fe/#290 프로젝트 구인 목록 페이지 퍼블리싱 및 알림기능 구현
- Loading branch information
Showing
22 changed files
with
1,071 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function Page() { | ||
return <div className="flex justify-center">OnBoarding Page</div> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
'use client' | ||
|
||
import { useInfiniteQuery } from '@tanstack/react-query' | ||
import Image from 'next/image' | ||
import { useRouter } from 'next/navigation' | ||
import { useEffect, useState } from 'react' | ||
import { useRecoilValue } from 'recoil' | ||
import MultipleFilter from '../../components/recruitment/main/MultipleFilter' | ||
import { PositionType } from '../../utils/types' | ||
import { | ||
multiplefilterState, | ||
keywordfilterState, | ||
recruitfilterState, | ||
directionState, | ||
} from '../../utils/atoms' | ||
import Filter from '../../components/recruitment/main/Filter' | ||
import RecruitmentCard from '../../components/recruitment/main/RecruitmentCard' | ||
import WriteIcon from '../../../public/images/svg/pencil-square.svg' | ||
|
||
export type RecruitmentDataType = { | ||
id: number | ||
nickname: string | ||
title: string | ||
position: PositionType | ||
techTags: string[] | ||
recruiting: boolean | ||
} | ||
|
||
export default function Recruitment() { | ||
const multipleFilter = useRecoilValue(multiplefilterState) | ||
const keywordFilter = useRecoilValue(keywordfilterState) | ||
const recruitFilter = useRecoilValue(recruitfilterState) | ||
const direction = useRecoilValue(directionState) | ||
|
||
const [postCount, setPostCount] = useState(0) | ||
const router = useRouter() | ||
|
||
function toWrite() { | ||
router.push('/project/write') | ||
} | ||
|
||
const getData = async ({ pageParam = 1 }) => { | ||
const params = new URLSearchParams() | ||
params.set('page', String(pageParam)) | ||
params.set('size', '12') | ||
params.set('direction', direction) | ||
|
||
const positions = multipleFilter | ||
.filter((v) => v.category === 'position') | ||
.map((v) => v.name) | ||
const skills = multipleFilter | ||
.filter((v) => v.category === 'skill') | ||
.map((v) => v.name) | ||
|
||
positions.forEach((position) => { | ||
params.append('positions', position) | ||
}) | ||
|
||
skills.forEach((skill) => { | ||
params.append('tags', skill) | ||
}) | ||
|
||
if (keywordFilter) { | ||
params.append('keyword', keywordFilter) | ||
} | ||
|
||
if (recruitFilter) { | ||
params.append('isRecruiting', String(recruitFilter)) | ||
} | ||
|
||
const res = await fetch( | ||
`${process.env.NEXT_PUBLIC_BASE_URL}/recruitments?${params.toString()}`, | ||
{ | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
}, | ||
) | ||
|
||
if (!res.ok) { | ||
const data = await res.json() | ||
throw new Error(data.message) | ||
} | ||
|
||
const data = await res.json() | ||
setPostCount(data.data.length) | ||
return data.data | ||
} | ||
|
||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = | ||
useInfiniteQuery( | ||
['recruitments', multipleFilter, keywordFilter, recruitFilter, direction], | ||
getData, | ||
{ | ||
getNextPageParam: (lastPage, pages) => | ||
lastPage.length < 12 ? undefined : pages.length + 1, | ||
}, | ||
) | ||
|
||
useEffect(() => { | ||
if (!isFetchingNextPage) { | ||
const handleScroll = () => { | ||
if ( | ||
document.documentElement.scrollHeight - | ||
document.documentElement.scrollTop === | ||
document.documentElement.clientHeight | ||
) { | ||
fetchNextPage() | ||
} | ||
} | ||
window.addEventListener('scroll', handleScroll) | ||
} | ||
}, [fetchNextPage, isFetchingNextPage, multipleFilter]) | ||
|
||
if (status === 'loading') { | ||
return <span>Loading...</span> | ||
} | ||
|
||
if (status === 'error') { | ||
return <span>Error fetching data</span> | ||
} | ||
|
||
return ( | ||
<div className="relative flex flex-col items-center w-screen h-auto min-h-screen pb-12 pt-28 "> | ||
<MultipleFilter /> | ||
{/* 프로젝트 공유 버튼 */} | ||
<button | ||
className="fixed z-10 flex flex-row items-center px-4 py-1 pt-3 pb-3 my-auto mb-2 font-semibold rounded-full bottom-10 right-10 shrink-0 bg-graphyblue text-slate-50 drop-shadow-md sm:invisible" | ||
onClick={() => toWrite()} | ||
aria-label="toWritePage" | ||
type="button" | ||
> | ||
<Image | ||
className="w-5 h-5 mr-2" | ||
src={WriteIcon} | ||
alt="WriteIcon" | ||
quality={50} | ||
/> | ||
<span className="font-semibold shrink-0">프로젝트 공유</span> | ||
</button> | ||
<Filter postCount={postCount} /> | ||
{data.pages.map((group, i) => ( | ||
<div | ||
className="relative flex flex-wrap justify-center mx-8" | ||
key={group[i]?.id} | ||
> | ||
{group.map((item: RecruitmentDataType) => ( | ||
<div className="mx-8" key={item.id}> | ||
<RecruitmentCard item={item} /> | ||
</div> | ||
))} | ||
</div> | ||
))} | ||
{hasNextPage && isFetchingNextPage && <span>Loading more...</span>} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,137 @@ | ||
'use client' | ||
|
||
import { useState, useEffect, useCallback } from 'react' | ||
import { useRouter, usePathname } from 'next/navigation' | ||
import { useRecoilState } from 'recoil' | ||
import Image from 'next/image' | ||
import { PiPaperPlaneTilt } from 'react-icons/pi' | ||
import { TfiBell } from 'react-icons/tfi' | ||
|
||
import WriteIcon from '../../../public/images/svg/pencil-square.svg' | ||
import ProfileIcon from '../../../public/images/svg/profileIcon.svg' | ||
import { usernameState } from '../../utils/atoms' | ||
import NoticeModal from '../recruitment/noticification/NoticeModal' | ||
|
||
export default function RecruitmentNavBar({ | ||
children, | ||
}: { | ||
children: React.ReactNode | ||
}) { | ||
return <div>{children}</div> | ||
const accessToken = | ||
typeof window !== 'undefined' ? sessionStorage.getItem('accessToken') : null | ||
const persistToken = | ||
typeof window !== 'undefined' ? localStorage.getItem('persistToken') : null | ||
const [username, setUsername] = useRecoilState(usernameState) | ||
const [btnText, setBtnText] = useState<string>('로그인') | ||
|
||
const [isOpenModal, setOpenModal] = useState<boolean>(false) | ||
|
||
const router = useRouter() | ||
const pathname = usePathname() | ||
|
||
const onClickToggleModal = useCallback(() => { | ||
setOpenModal(!isOpenModal) | ||
}, [isOpenModal]) | ||
|
||
function signOut() { | ||
setUsername('') | ||
if (accessToken) { | ||
sessionStorage.removeItem('accessToken') | ||
} else { | ||
localStorage.removeItem('persistToken') | ||
} | ||
} | ||
|
||
const handleLogin = () => { | ||
if (accessToken || persistToken) { | ||
signOut() | ||
setBtnText('로그인') | ||
} else { | ||
router.push('/project/login') | ||
} | ||
} | ||
|
||
useEffect(() => { | ||
if (accessToken || persistToken) { | ||
setBtnText('로그아웃') | ||
} else { | ||
setBtnText('로그인') | ||
} | ||
}, [pathname]) | ||
|
||
if (pathname === '/project/login' || pathname === '/project/registration') { | ||
return children | ||
} | ||
|
||
return ( | ||
<div> | ||
<div className="fixed z-20 mb-5 w-screen flex justify-between content-center overflow-hidden border-b border-zinc-400 bg-white pt-3 pb-3 px-8 align-middle font-ng-eb"> | ||
{/* 로고 */} | ||
<button | ||
onClick={() => router.push('/')} | ||
className="hidden font-lato-b text-4xl text-graphyblue sm:block" | ||
type="button" | ||
> | ||
Graphy | ||
</button> | ||
<button | ||
onClick={() => router.push('/')} | ||
className="font-lato-b text-4xl text-graphyblue sm:hidden" | ||
type="button" | ||
> | ||
G | ||
</button> | ||
<div className="flex"> | ||
<button | ||
className=" mr-4 whitespace-nowrap rounded-full bg-graphyblue px-4 text-white" | ||
type="button" | ||
onClick={() => handleLogin()} | ||
> | ||
{btnText} | ||
</button> | ||
|
||
{/* 프로젝트 작성 버튼 */} | ||
<button | ||
className="invisible mx-auto mr-4 flex h-0 w-0 shrink-0 flex-row flex-nowrap items-center rounded-full bg-graphyblue text-white sm:visible sm:mr-5 | ||
sm:h-auto sm:w-auto sm:px-4 sm:py-1" | ||
onClick={() => router.push('/project/new-post')} | ||
aria-label="toWritePage" | ||
type="button" | ||
> | ||
<Image className="mr-2 h-5 w-5" src={WriteIcon} alt="WriteIcon" /> | ||
<span className="font-semibold">프로젝트 공유</span> | ||
</button> | ||
|
||
<div className="flex h-full items-between relative gap-4"> | ||
<button type="button" className="text-[10px] font-light"> | ||
<PiPaperPlaneTilt size="28" /> | ||
</button> | ||
<button | ||
onClick={onClickToggleModal} | ||
type="button" | ||
className="text-[10px] font-light" | ||
> | ||
<TfiBell size="28" /> | ||
</button> | ||
{/* 마이페이지 아이콘 */} | ||
<button | ||
style={{ display: btnText === '로그인' ? 'none' : 'block' }} | ||
type="button" | ||
onClick={() => router.push(`/project/profile/${username}`)} | ||
> | ||
<Image | ||
className="h-8 w-8 appearance-none" | ||
src={ProfileIcon} | ||
alt="ProfileIcon" | ||
/> | ||
</button> | ||
</div> | ||
</div> | ||
</div> | ||
{isOpenModal ? ( | ||
<NoticeModal onClickToggleModal={onClickToggleModal} /> | ||
) : null} | ||
{children} | ||
</div> | ||
) | ||
} |
Oops, something went wrong.