Project/Airbnb Clone

[Airbnb 클론코딩] Category UI

hu6r1s 2023. 7. 2. 17:50

fmf2023.06.25 - [Project/Airbnb Clone] - [Airbnb 클론코딩] Register & Login functionality

 

[Airbnb 클론코딩] Register & Login functionality

2023.06.17 - [Project/Airbnb Clone] - [Airbnb 클론코딩] Auth UI [Airbnb 클론코딩] Auth UI 2023.06.11 - [Project/Airbnb Clone] - [Airbnb 클론코딩] 환경설정 및 Navbar UI(With. Youtube) [Airbnb 클론코딩] 환경설정 및 Navbar UI(With. Y

hu-bris.tistory.com

Category UI

카테고리를 만들어야하는데 전에 만들었던 Navbar.tsx 파일을 통해 카테고리 컴포넌트를 넣어준다.

<div className='fixed w-full bg-white z-10 shadow-sm'>
      <div className='py-4 border-b-[1px]'>
        <Container>
          <div className="
            flex
            flex-row
            items-center
            justify-between
            gap-3
            md:gap-0
          ">
            <Logo />
            <Search />
            <UserMenu currentUser={currentUser} />
          </div>
        </Container>
      </div>
      <Categories />	// 카테고리 컴포넌트
    </div>

이제 카테고리 컴포넌트를 만들어보자..

'use client';

import Container from '../Container';
import { TbBeach, TbMountain, TbPool } from 'react-icons/tb';
import {
  GiWindmill,
  GiIsland,
  GiBoatFishing,
  GiCastle,
  GiCaveEntrance,
  GiForestCamp,
  GiCactus,
  GiBarn
} from 'react-icons/gi';
import { MdOutlineVilla } from 'react-icons/md';
import { FaSkiing } from 'react-icons/fa';
import { BsSnow } from 'react-icons/bs';
import { IoDiamond } from 'react-icons/io5'
import CategoryBox from '../CategoryBox';
import { usePathname, useSearchParams } from 'next/navigation';


export const categories = [
  {
    label: 'Beach',
    icon: TbBeach,
    description: 'This property is close to the beach!'
  },
  {
    label: 'Windmills',
    icon: GiWindmill,
    description: 'This property has windmills!'
  },
  {
    label: 'Modern',
    icon: MdOutlineVilla,
    description: 'This property is modern!'
  }
]

const Categories = () => {
  const params = useSearchParams()
  const category = params?.get('category')
  const pathname = usePathname()

  const isMainPage = pathname === '/'

  if (!isMainPage) {
    return null
  }
  return (
    <Container>
      <div
        className="
          pt-4
          flex
          flex-row
          items-center
          justify-between
          overflow-x-auto
        "
      >
        {categories.map((item) => (
          <CategoryBox
            key={item.label}
            label={item.label}
            selected={category === item.label}
            icon={item.icon}
          />
        ))}
      </div>
    </Container>
  );
};

export default Categories;

카테고리 데이터를 그냥 배열 형식으로 만들고 이를 map 함수를 통해 하나씩 뽑아 출력하도록 했다.

데이터가 많아 글이 길어질까봐 몇 개 빼고 올렸다.

category라는 파라미터에 오는 값이 해당 카테고리 라벨과 같다면 selected props가 true가 되면서 outline이 나오게 될 것이다.

다음은 CategoryBox이다.

CategoryBox

먼저 필요한 라이브러리를 설치해야 한다.

npm install query-string

URL 쿼리를 파싱하고 문자열화시키는 라이브러리이다.

import { useRouter, useSearchParams } from "next/navigation";
import { useCallback } from "react";
import { IconType } from "react-icons";
import qs from "query-string";

interface CategoryBoxProps {
  icon: IconType;
  label: string;
  selected?: boolean;
}

const CategoryBox: React.FC<CategoryBoxProps> = ({
  icon: Icon,
  label,
  selected
}) => {
  const router = useRouter()
  const params = useSearchParams()

  const handleClick = useCallback(() => {
    let currentQuery = {}

    if (params) {
      currentQuery = qs.parse(params.toString())
    }

    const updatedQuery: any = {
      ...currentQuery,
      category: label
    }

    if (params?.get('category') === label) {
      delete updatedQuery.category
    }

    const url = qs.stringifyUrl({
      url: '/',
      query: updatedQuery
    }, { skipNull: true })

    router.push(url)
  }, [])
  return (
    <div
      onClick={handleClick}
      className={`
        flex
        flex-col
        items-center
        justify-center
        gap-2
        p-3
        border-b-2
        hover:text-neutral-800
        transition
        cursor-pointer
        ${selected ? 'border-b-neutral-800' : 'border-transparent'}
        ${selected ? 'text-neutral-800' : 'text-neutral-500'}
      `}
    >
      <Icon size={26} />
      <div className="font-medium text-sm">
        {label}
      </div>
    </div>
  );
};

export default CategoryBox;

쿼리를 가져오고 업데이트된 쿼리로는 현재 쿼리에 category 키에 label 값을 넣는다.

category 파라미터가 label과 같으면 업데이트된 쿼리의 category를 지운다. 그리고 이를 '/{updatedQuery}'를 문자열화 시켜 해당 페이지로 이동시킨다.

Login & Register Toggle

이전에 만든 로그인과 회원가입 모달창에 수정을 해야하는 부분이 있다.

푸터에 로그인 페이지에서 계정이 없으면 회원가입 페이지로, 회원가입 페이지에서 계정이 있으면 로그인 페이지로 토글시키는 것이다.

// LoginModal.tsx
const toggle = useCallback(() => {
    loginModal.onClose()
    registerModal.onOpen()
  }, [loginModal, registerModal])
  
  // RegisterModal.tsx
  const toggle = useCallback(() => {
    registerModal.onClose()
    loginModal.onOpen()
  }, [registerModal, loginModal])

해당 코드들을 각각의 파일에 넣고 onClick 속성을 이용해 사용해주면 된다.