6. 게시판 NavBar 구현 / 게시판 홈 화면 구성

박준혁·2023년 6월 6일
0

본격적으로 게시판의 구체적인 기능들을 구현하기 위해 먼저 게시판에서 사용될 NavBar(상단과 좌측에 위치한 목록들)과 게시판 홈 화면을 구성 하는 것까지 정리해보고자 한다.

게시판 NavBar

게시판의 NavBar는 크게 상단과 좌측에 2개가 들어간다. 각각의 대략적인 디자인은 앞선 게시글의 레이아웃에서 확인할 수 있다. 이번 과정에서는 구체적으로 각각의 NavBar를 구상하고 구현까지 해보도록 할 계획이다.

상단 NavBar (PostNavBar)

상단 NavBar은 현재 로그인한 회원의 정보들을 보여주고, 로그아웃 버튼과 메인 페이지로 돌아갈 수 있는 버튼을 포함하고 있다. 코드는 아래와 같다. 동아리명을 누르면 메인페이지로 돌아간다. 또한 사용자의 이름(기수), 팀명, 소속 학과 순으로 사용자의 정보를 session에서 가져와 나타낸다. 마지막은 login, logout 버튼 구현할 때 같이 만들어둔 ExitLogOutBtn을 import해서 누를 경우 로그아웃 하는 버튼을 추가하였다.

<div>
            <Link href={'/'}>V E S S</Link>
            <div>
                <span>{`${session.user.name} (${session.user.semester}기)`}</span>
                <span>{session.user.teamname}</span>
                <div>
                    <span>{session.user.group}</span>
                    <ExitLogOutBtn/>
                </div>
            </div>
</div>

구현 결과

구현 결과는 아래와 같다.

좌측 NavBar (TeamNavBar)

이번에는 좌측 NavBar인 팀별로 구분할 수 있는 NavBar를 제작하는 것이다. 각각의 버튼을 누르면 해당 팀에서 작성한 게시글만 확인할 수 있는 /post/(기수)th(팀번호)로 이동하게 된다. All을 누를 경우 /post (게시판 홈 화면)으로 이동하게 된다. 간단하게 링크만 구현해도 되지만 효과를 주고 싶어서 현재 활성화되어있는 항목의 색을 다르게 구현하기로 하였다. (만약 게시판 메인화면에 있다면 All이 다르게 표현되고, 현재 1조 링크에 들어간 상태라면 1조 항목의 색이 다르게 표현되는 방식이다.)

usePathname

해당 방식 구현을 위해 여러 방식의 router를 검색하였다. 그 결과 React에서 useRouter 사용하는 것과 다르게 Next.js에서 따로 제공하는 Router가 있다.'next/navigation' 라이브러리 내부에 usePathname을 사용하면 현재 어떠한 주소로 라우팅 되어있는지 확인 가능하다고 한다. 예를 들어 게시판 홈 화면으로 들어오면 '/post'를 return 하고, 8기 1조 링크로 들어가면 '/post/8th1'를 얻을 수 있는 방식이다. 해당 함수를 이용해 TeamNavBar를 구현해보고자 한다.

usePathname 공식 Document

TeamNavBar 코드

아래 코드는 export하는 TeamNavBar 최종 함수 형태이다. temp에 usePathname()을 활용해 경로를 저장하고, 해당 경로가 변화할 때마다 useEffect를 활용해 pathname이라는 변수에 temp를 다시 저장해준다. 해당 pathname은 TeamNavBarComp에 props로 전달한다. TeamNavBarComp는 아래에서 더 살펴보자.

'use client'
export default function TeamNavBar() {
    const temp = usePathname();
    let pathname = temp;
    useEffect(()=>{
        pathname=temp;
    },[temp])
    return (
        <TeamNavBarComp router={pathname}/>
    )
}

아래는 TeamLink와 TeamNavBarComp라는 함수들에 대한 코드이다. 먼저 TeamLink는 각각의 팀별 링크를 return 하는 함수로, 다양한 값들을 인자로 받고 있다. teamname은 화면에 나타나는 팀 이름, path는 실제 게시판 경로, className은 각각에 적용할 class들, router_pathname은 router에서 가져온 현재 접속중인 사이트의 pathname이다.(위에서 props로 전달한 값) accurate는 정확도 여부를 확인하는 것으로 값이 true이면 pathname이 path와 완전히 일치하는 것인지 확인하고, false이면, 해당 경로 이름으로 시작하는지만(startsWith 이용) 확인하는 방식이다. (이 방식을 사용한 이유는 All의 경우 경로가 '/post'라 다른 경로들과 startsWith이 겹치기 때문에 구분하기 위해서 All의 경우에만 accurate를 true로 사용한다.)

const TeamLink = ({teamname, path, className, accurate = false, router_pathname}) => {
    if(accurate){
        return(
            <Link href={path} 
            className={router_pathname==path ? activeteamstyle : teamstyle}>
                {teamname}
            </Link>
        )
    }
    return (
        <Link href={path} 
        className={router_pathname.startsWith(path) ? activeteamstyle : teamstyle}>
            {teamname}
        </Link>
    )
}

TeamNavBarComp 코드

앞서 제작한 TeamLink를 이용해 TeamNavBarComp를 제작한다. 해당 함수는 외부에 export할 TeamNavBar에서 usePathname을 이용해 가져온 이름을 props로 전달받는다. 각각 팀명과 경로, usePathname에서 받은 값을 전달하고있고, 앞서 말한 것과 같이 All에 대해서 accurate를 true로 지정하였다.


function TeamNavBarComp({router}) {
    return (
        <div>
            <TeamLink teamname={'All'} path='/post' router_pathname={router} accurate={true} />
            <div>
                <div>7.5th VESS</div>
                <div>
                    <TeamLink teamname={'재잘재잘'} path='/post/7.5th1' router_pathname={router} />
                    <TeamLink teamname={'Fancy'} path='/post/7.5th2' router_pathname={router}/>
                </div>
            </div>
            <div>
                <div>8th VESS</div>
                <div>
                    <TeamLink teamname={'1조'} path='/post/8th1' router_pathname={router}/>
                    <TeamLink teamname={'2조'} path='/post/8th2' router_pathname={router}/>
                    <TeamLink teamname={'3조'} path='/post/8th3' router_pathname={router}/>
                    <TeamLink teamname={'4조'} path='/post/8th4' router_pathname={router}/>
                </div>
            </div>
        </div>
    )
}

구현 결과

구현 결과는 아래와 같다.

post 게시판 홈 화면 구성

앞에서 구현한 두 종류의 NavBar를 이용해 post 게시판의 레이아웃을 구성하고 로그인 여부에 따라서 화면의 구성을 변화하고자 한다. 추가적으로 홈 화면에서는 MongoDB에 저장한 글 목록을 불러와서 글 목록까지 나타내보고자 한다.

레이아웃 구성

레이아웃은 현재 만들어둔 post 폴더 내부에 layout.js 파일을 만들어 구성하면 된다.

로그인 여부에 따라 화면 변경

앞선 게시글에서 확인할 수 있는 것처럼 server component에서는 session을 호출하여 로그인 여부를 확인할 수 있다. 따라서 아래와 같이 코드를 작성하면 된다. 로그인한 경우에 children들을 나타내고, 로그인하지 않은 경우는 else에서와 같이 LogIn Required라는 메시지를 보게 된다.

let session = await getServerSession(authOptions)
if(session){
       return (
            <html lang="en">
              <body>
                	{children}
              </body>
            </html>
          )
    }
    else{
        return(
            <body>LogIn Required</body>
        )
    }

구현한 결과 로그인을 하지 않은 상태로 /post 로 접속하면 강제로 다음과 같은 메시지가 나타나며 로그인이 필요하다고 나타난다.

다음은 앞서 제작한 NavBar를 이용하는 과정이다. 이 또한 굉장히 간단하다. 위에서 제작한 layout.js 파일에 NavBar 두가지를 import 하여 사용하면 된다. /post 를 포함하는 모든 주소에 접근했을 때 나타나게 할 것이기 때문에 해당 파일을 수정하면 되고, if문 내부에 NavBar 2개를 추가하면 된다. 결과는 아래와 같다.

홈 화면 구성

다음은 홈 화면을 구성할 차례이다. 위와 같이 All이 활성화 된 홈 화면에서는 저장한 모든 게시글을 불러와 차례로 나타내면 된다. 게시글은 MongoDB 내부에 post라는 collection 내부에 저장할 것이며 현재는 직접 MongoDB 내부에 데이터 몇개를 저장하여 테스트하고자 한다. 이후에 게시글 작성 등은 구현할 예정이다.

구현 코드

먼저 /post의 홈 화면에서 표현할 page.js 파일은 아래와 같이 작성하였다. db를 연결한 다음에 result에 post collection의 정보들을 배열로 저장한 다음에 reverse 시킨다. 배열에는 정보가 추가된 시간이 이른 것부터 나타나기 때문에 reverse를 통해 최근 게시글부터 확인할 수 있도록 하기 위해서이다.

//page.js
export default async function Home () {
    const db = (await connectDB).db('vessweb');
    let result = await db.collection('post').find().toArray();
    result = result.reverse();
    return (
        <div>
            <ListItem result={result} />
        </div>
    )
}

위에서 가져온 배열을 props로 받아 실제 글 목록을 출력하는 ListItem 함수는 아래와 같다. export하는 ListItem function은 result의 element 각각을 map을 통해 PostListDiv라는 새로운 함수로 보내는 역할을 한다. 구체적으로 PostListDiv 함수에서는 각각의 element에서 게시글 정보를 가져와 제목, 팀명과 글을 처음 작성한 사람, 처음 작성된 시간을 나타낸다. 해당 글을 누를 경우 Link를 통해 다른 주소로 이동하며, 해당 주소는 글의 세부 내용을 볼 수 있게 된다. 주소의 구성은 앞서 팀별 글을 구분할 때 사용하던 /post/(기수)/(팀번호) 를 기본으로 하고 뒤에 mongoDB에서 저장할 때 사용하는 _id를 이용하여 나타내고 있다. 이후에 해당 주소에서 글의 세부 정보를 확인할 수 있도록 제작할 것이다.

//ListItem.js
const PostListDiv = (item) => {
    return(
        <div>
            <Link href={`/post/${item.semester}th${item.team}/${item._id}`} >
                <h1>{item.title}</h1>
                <div>
                    <span>{item.teamname}({item.FirstAuthor})</span>
                    <span>{item.firstCreated}</span>
                </div>
            </Link>
        </div>
    )
}

export default function ListItem({result}){
    return(
        <>
            {
                result.map((item)=>{
                    return PostListDiv(item);
                })
            }
        </>
    )
}

구현 결과

게시판 홈 화면을 구현한 결과는 아래와 같다. 2번째 글의 경우 마우스를 hover 시켰을 때 위치가 이동하고 그림자 및 색상 효과가 적용된 상태의 예시이다.

0개의 댓글