PJH's Community Site - Community(2)

박정호·2022년 11월 27일
0

Community Project

목록 보기
7/14
post-thumbnail

🚀 Start

이제 커뮤니티들이 게시되는 메인페이지를 생성해보자.



⭐️ Navigation Bar

✔️ Navigation Bar 생성

파일 생성



✔️ Navigation Bar UI 작성

// navBar.tsx
const NavBar = () => {
  const { loading, authenticated } = useAuthState();

	...

  return (
   		 <div>
      		<span>
       		 <Link href="/">
          		<a>
            	<Image alt="logo" width={80} height={45}></Image>
          		</a>
        	</Link>
     	 	</span>
            <div>
              <div>
                <FaSearch/>
                <input type="text" placeholder="Search Reddit"/>
              </div>
            </div>

            <div>
              {!loading &&
                (authenticated ? (
                  <button onClick={handleLogout}>로그아웃</button>
                ) : (
                  <>
                    <Link href="/login">
                      <a>로그인</a>
                    </Link>
                    <Link href="/register">
                      <a>회원가입</a>
                    </Link>
                  </>
                ))}
            </div>
   		 </div>
  );
};

export default NavBar;


✔️ Not Navbar in 로그인,회원가입

로그인과 회원가입 페이지에서는 Navigation Bar가 필요하지 않으므로 그에 맞는 설정을 해준다.

1️⃣ 만약 현재 페이지가 register, login이라면 authRoutetrue가 될 것이고, 그렇지 않다면 false가 될 것이다.

💡 useRouter란?

2️⃣ authRout가 true라면 즉, 로그인,회원가입 페이지라면 Navbar을 보이지 않게, 아니라면 Navbar을 보이게 한다.

3️⃣ Navbar의 존재에 따라 컴포넌트가 위아래로 움직이는 것을 막기 위해 paddingTop을 준다.

💡 pageProps?

// _app.tsx
  export default function App({ Component, pageProps }: AppProps) {
	...

  // 1️⃣ 번
  const { pathname } = useRouter();
  const authRoutes = ['/register', '/login'];
  const authRoute = authRoutes.includes(pathname);

  return (
    <AuthProvider>
      {!authRoute && <NavBar />} // 2️⃣ 번
      <div className={authRoute ? '' : 'pt-12'}> // 3️⃣ 번
        <Component {...pageProps} />
      </div>
    </AuthProvider>
  );
}


⭐️ logout

✔️ logout 기능 구현

로그인시에는 Navbar에 로그아웃이라는 버튼이 나타날 것이다. 이제 로그아웃버튼을 눌렀을 때 실제로 로그아웃 기능이 동작하도록 구현해보자.

handleLogout(로그아웃버튼클릭) 실행시 api요청 후 결과값이 전달되면 LOGOUT이라는 액션이 동작하도록 dispatch. 그리고 화면 리로딩

// navBar.tsx
 ...
 const dispatch = useAuthDispatch();

  const handleLogout = () => {
    axios
      .post('/auth/logout')
      .then(() => {
        dispatch('LOGOUT');
        window.location.reload();
      })
      .catch(error => {
        console.log(error);
      });
  };


✔️ 로그아웃을 위한 api 생성

client에서 로그아웃에 대한 api 요청을 하였으므로 그에 대한 response을 보내주면 된다. 로그아웃을 하는 것은 간단하다. 로그인은 토큰에 의해 인증처리가 되었던 것임으로 로그아웃 버튼 클릭시 토큰을 없애주면 된다.

  • expires: new Date(0) : 만료 날짜를 0으로 하여 즉시 토큰 인증이 만료되게 한다

💡 How to Use Cookies with JavaScript

src/routes/auth.ts


const logout = async (_: Request, res: Response) => {
  res.set(
    'Set-Cookie',
    cookie.serialize('token', '', {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',
      expires: new Date(0),
      path: '/',
    })
  );

  res.status(200).json({ success: true });
};

const router = Router();
...
router.post('/logout', userMiddleware, authMiddleware, logout);



⭐️ Community List

이제 작성한 커뮤니티들이 등록되어 나타나지는 커뮤니티 목록을 생성해보자!



✔️ List - 레이아웃 UI 생성


// index.tsx
const Home: NextPage = () => {
  return (
    <div>
      {/* 포스트 리스트 */}
      <div></div>

      {/* 사이드바 */}
      <div>
        <div>
          <div>
            <p>상위 커뮤니티</p>
          </div>

          {/* 커뮤니티 리스트 */}
             <div></div>

          <Link href="/subs/create"> 커뮤니티 만들기</Link>
        </div>
      </div>
    </div>
  );
};


✔️ 커뮤니티 데이터 가져오기

작성한 커뮤니티들을 이제 리스트에 가져와서 보여주는 것을 해야하는데, 데이터를 가져오기 위한 React Hooks인 SWR(stale-while-revalidate)을 사용해보자.

설치

//client
npm install swr --save

api 요청 (client)

1️⃣ 비동기통신 라이브러리인 axios를 작성한 비동기함수를 fetcher에 저장.

2️⃣ API URL을 address에 저장. (topSubs라는 핸들러가 백엔드에 존재)

3️⃣ useSWR hook은 key 문자열fetcher 함수를 받는다.

  • key는 데이터의 고유한 식별자이며(일반적으로 API URL) fetcher로 전달될 것.
  • fetcher는 데이터를 반환하는 어떠한 비동기 함수도 될 수 있다.
// index.tsx
const Home: NextPage = () => {
  const fetcher = async (url: string) => {
    return await axios.get(url).then(res => res.data);
  };
  const address = 'http://localhost:4000/api/subs/sub/topSubs';
  const { data: topSubs } = useSWR<Sub[]>(address, fetcher);

💡 type 지정

export interface Sub {
  createdAt: string;
  updatedAt: string;
  name: string;
  title: string;
  description: string;
  imageUrn: string;
  bannerUrn: string;
  username: string;
  posts: Post[];
  postCount?: string;

  imageUrl: string;
  bannerUrl: string;
}
export interface Post {
  identifier: string;
  title: string;
  slug: string;
  body: string;
  subName: string;
  username: string;
  createdAt: string;
  updatedAt: string;
  sub?: Sub;

  url: string;
  userVote?: number;
  votesScore?: number;
  commentCount?: number;
}

topSubs 핸들러 (server)

  • 원래는 다음과 같이 쿼리를 작성한다.

  • 하지만,QueryBuilderTypeORM의 가장 강력한 기능중 하나로, QueryBuilder는 우아하고 편리한 문법으로 SQL Query를 생성하고 실행한 다음 자동적으로 변형된 Entity를 반환해줌으로 다음과 같이 작성해보자.

// subs.ts
const topSubs = async (_: Request, res: Response) => {
  try {
    // COALESCE: 처음으로 NULL이 아닌 컬럼값을 만나면 그 컬럼 값을 리턴
    const imageUrlExp = `COALESCE('${process.env.APP_URL}/images/'
	||s."imageUrn",'https://www.gravatar.com/avatar?d=mp&f=y')`;
    
    // createQueryBuilder를 사용해서 Query문을 사용
    const subs = await AppDataSource.createQueryBuilder()
     
      .select//테이블에 있는 데이터(제목,이름,이미지,id)를 조회
        `s.title, s.name, ${imageUrlExp} as "imageUrl", count(p.id) as "postCount"`)
      .from(Sub, 's') // Sub 테이블에서 데이터를 조회
      .leftJoin(Post, 'p', `s.name = p."subName"`)//왼쪽 테이블을 중심으로 오른쪽의 테이블을 매치
      .groupBy('s.title, s.name, "imageUrl"')// 유형별로 갯수를 조회(데이터를 그룹화)
      .orderBy(`"postCount"`, 'DESC') // 데이터 내림차순 정렬
      .limit(5) // 5개까지만 데이터 조회
      .execute(); // EXECUTE: 준비된 SQL문을 실행
    return res.json(subs); // 데이터들을 JSON 데이터로 client에 return(전달)
  } catch (error) {
    console.log(error);
    return res.status(500).json({ error: '문제가 발생했습니다.' });
  }
};

router.post('/sub/topSubs', topSubs); 

💡 www.gravatar.com

  • 이미지 업로드 생략시 기본적으로 나오는 이미지를 불러오는 url로, 이미지를 사용하기위해 설정을 해주자.
// next.config.js
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
   images: {
    domains: ['www.gravatar.com', 'localhost'],
  },
};
module.exports = nextConfig;

💡 잠깐) SWR?

데이터를 가져오기 위한 React Hook 라이브러리.

SWR은 원경 데이터를 가져올 때 캐싱된 데이터가 있으면 그 데이터를 먼저 반환(stale)한 다음 가져오기 요청(revaildate)을 보내고, 마지막으로 최신 데이터와 함께 제공하는 라이브러리이다.

  • SWR 특징 및 장점
  • 사용법

    • useSWR로 React Hook으로, 주된 인자로 key와 fetcher가 있다. 첫 번째 인자는 API URL이면서 캐싱할 때 사용되는 key가 된다. 이는 useSWR('/api/user/123’, fetcher)를 여러 컴포넌트에서 사용하여도 같은 key의 데이터가 있다면 캐싱된 것을 가져오는 것을 말한다.

    • 두 번째 인자는 fetcher이다. Fetch API를 기본으로 하며, 제일 많이 사용되는 Axios 나 GraphQL을 사용할 수 있다.

👉 참고하자!



✔️ List - 리스트 UI 생성

1️⃣ 앞서 본 topSubs 핸들러를 통해 테이블의 데이터가 client로 전달되었고, 그 값이 data: topSubs에 저장

2️⃣ 전달받은 데이터들을 map을 통해 출력하고, 각각 데이터(커뮤니티)들마다 커뮤니티 이름을 가진 고유의 url을 가지게 된다.

const Home: NextPage = () => {
  
  const { data: topSubs } = useSWR<Sub[]>(address, fetcher); // 1️⃣ 번
  return (
    <div>
     ...

        	// 커뮤니티 리스트
               <div>
            {topSubs?.map((sub) => ( // 2️⃣ 번
              <div key={sub.name}>
                <Link href={`/p/${sub.name}`}>
                  <Image 
					src={sub.imageUrl}
                    alt="Sub"
                    width={24}
                    height={24}
                    />
                 </Link>
                 <Link href={`/p/${sub.name}`}>
                   {sub.name}
                 </Link>
                <p>{sub.postCount}</p>
              </div>
            ))}


          </div>

    </div>
  );
};


✔️ 커뮤니티 생성 버튼 인증처리

커뮤니티 생성은 로그인시에만 가능함으로 커뮤니티 생성버튼이 로그인시에만 작동하도록 설정.

const Home: NextPage = () => {
  const { authenticated } = useAuthState();
 return(
     {authenticated && (
                <div className="w-full py-6 text-center">
                  <Link href="/subs/create">
                    커뮤니티 만들기
                  </Link>
                </div>
     )}
  )
}


📷 Photos

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글

관련 채용 정보