오른쪽 Navbar 상단위에 유저 정보를 클릭시 로그인, 회원가입 드랍다운이 가능하도록 구현하였다.

Ant design + tailwindcss를 사용하였으며, 코드는 다음과 같다.

 

navbar.tsx

import styled from "@emotion/styled";
import { Modal } from "antd";
import { ReactNode } from "react";
import Line from "../../../common/liine/line";

interface BaseModalProps {
  children: ReactNode;
  isModalOpen: boolean;
  title: string;
  onCancel: () => void;
}

const AuthModal = styled(Modal)`
  .ant-modal-title {
    font-size: 15epx;
    text-align: center;
  }
`;

function Footer(): JSX.Element {
  return (
    <>
      <div className="flex justify-center">
        <button className="w-full h-12 text-white bg-pink-700 rounded-lg border-none">
          계속
        </button>
      </div>
    </>
  );
}

export default function AuthLayout({
  children,
  title,
  isModalOpen,
  onCancel,
}: BaseModalProps): JSX.Element {
  return (
    <>
      <AuthModal
        title={title}
        open={isModalOpen}
        centered={true}
        width={530}
        footer={Footer}
        onCancel={onCancel}
      >
        <Line />
        <h2 className="text-xl font-semibold mt-5">
          에어비엔비에 오신걸 환영합니다.
        </h2>
        {children}
      </AuthModal>
    </>
  );
}

 

user.tsx

 

import { useState } from "react";
import LoginModal from "../../units/modals/auth/login_modal";
import SignupModal from "../../units/modals/auth/signup_modal";
import Search from "./search";
import User from "./user";

export default function Navbar(): JSX.Element {
  const [loginModalOpend, setLoginModalOpend] = useState(false);
  const [signupModalOpend, setSignupModalOpend] = useState(false);
  const handleSignup = (): void => {
    setSignupModalOpend((prev) => !prev);
  };

  const handleLogin = (): void => {
    setLoginModalOpend((prev) => !prev);
  };

  return (
    <>
      <nav className="w-full h-40 bg-white font-semibold pt-4">
        <div className="flex justify-between items-center">
          <h2 className="text-red-500 text-2xl">airbnb</h2>
          <User handleSignup={handleSignup} handleLogin={handleLogin} />
        </div>
        <Search />
      </nav>
      {loginModalOpend ? (
        <LoginModal
          loginModalOpend={loginModalOpend}
          handleLogin={handleLogin}
        />
      ) : (
        ""
      )}
      {signupModalOpend ? (
        <SignupModal
          signupModalOpend={signupModalOpend}
          handleSignup={handleSignup}
        />
      ) : (
        ""
      )}
    </>
  );
}

 

 

실제 결과

 

 

 

<모달 구현>

 

layout.tsx

import { faBars } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React from "react";
import { Dropdown, Space, Typography } from "antd";
import type { MenuProps } from "antd";

interface UserProps {
  handleSignup: () => void;
  handleLogin: () => void;
}

export default function User({
  handleSignup,
  handleLogin,
}: UserProps): JSX.Element {
  const items: MenuProps["items"] = [
    {
      key: "1",
      label: "로그인",
      style: { height: "50%", backgroundColor: "white", color: "black" },
      onClick: () => {
        handleLogin();
      },
    },
    {
      key: "2",
      label: "회원가입",
      style: {
        height: "50%",
        backgroundColor: "white",
        color: "black",
      },
      onClick: () => {
        handleSignup();
      },
    },
  ];

  return (
    <>
      <Dropdown
        menu={{
          items,
          selectable: true,
          defaultSelectedKeys: ["1"],
          style: { width: "220px", height: "100px" },
        }}
      >
        <Typography.Link>
          <Space>
            <button className="w-24 h-12 flex justify-between items-center border rounded-full pl-3 pr-3">
              <FontAwesomeIcon icon={faBars} />
              <image className="w-1/2 h-3/4 flex justify-center items-center bg-black rounded-full text-white text-sm">
                U
              </image>
            </button>
          </Space>
        </Typography.Link>
      </Dropdown>
    </>
  );
}

 

signup_modal.tsx

 

import { Input } from "antd";
import AuthLayout from "./layout";

interface SignupProps {
  signupModalOpend: boolean;
  handleSignup: () => void;
}

export default function SignupModal({
  signupModalOpend,
  handleSignup,
}: SignupProps): JSX.Element {
  return (
    <>
      <AuthLayout
        title="로그인 또는 회원가입"
        isModalOpen={signupModalOpend}
        onCancel={handleSignup}
      >
        <form className="space-y-3 mt-5">
          <Input placeholder="이메일" className="h-10" />
          <Input placeholder="이름" className="h-10" />
          <Input placeholder="휴대폰번호" className="h-10" />
          <Input.Password placeholder="패스워드" className="h-10" />
          <Input.Password placeholder="패스워드 확인" className="h-10" />
        </form>
      </AuthLayout>
    </>
  );
}

 

login_modal.tsx

import { Input } from "antd";
import AuthLayout from "./layout";

interface LoginProps {
  loginModalOpend: boolean;
  handleLogin: () => void;
}

export default function LoginModal({
  loginModalOpend,
  handleLogin,
}: LoginProps): JSX.Element {
  return (
    <>
      <AuthLayout
        title="로그인 또는 회원가입"
        isModalOpen={loginModalOpend}
        onCancel={handleLogin}
      >
        <form className="space-y-3 mt-4">
          <Input placeholder="이메일" className="h-10" />
          <Input.Password placeholder="패스워드" className="h-10" />
        </form>
      </AuthLayout>
    </>
  );
}

 

 

 

결과

회원가입 모달

 

사진1

 

사진2
사진3

 

에어비엔비 tailwindcss를 활용하여, Room Detail 페이지를 구현하였다. 전반적인 틀만 만들었고, 기능구현하면서 따로 스타일링을 추가적으로 할예정이다. 

Home 화면

 

Next js를 활용하여 에어비엔비 클론코딩을 시작하였다. 먼저 홈에 대한 화면 스타일링을 구축하기 위해 기본적인 틀을 만들었다. Fontawesome + tailwindcss를 활용하여 예쁘게 만들었다. 이 후 계속해서 수정해나갈예정

framer-motion, emotion, next js 버전으로 인해 문제가 발생한거 같다.


(이전 사용 -> 변경 후 버전)

next : 12.1.0 -> 12.0.1
framer-motion: 11버전(정확히 기억이안남...) -> "5.0.1",
emotion/react: "^11.10.5" -> "11.8.1"
emotion/styled: "^11.11.0" -> "11.6.0"


참고 URL

https://github.com/vercel/next.js/issues/30750

인프런에서 코드 캠프인강을 들으면서 최근에 Next js를 공부를 다시 시작하고있다.
해당 강의에서는 graphql을 사용하는데, rest api으로도 실습을 하고싶어서 간단하게 express를 사용해서 백엔드 서버를 구축하였다.
간단한 게시판을 만들고 있는데, 글의 detail 페이지를 만드는데, data를 가져오는 과정에서 문제를 겪었다. 

 

일단 먼저 프론트에서는 pages/boards/new/[boardId] BoardDetailPage 컴포넌트가 있고,

백엔드에서 /api/posts/1, /api/posts/2, /api/posts/:id와 같은 board의 정보를 담고있는 api가 있다.

useRotuer의 router.query를 사용하여, pages/boards/new/[boardId]에서 boardId를 가져왔다.
그러고 난후에, boardId값을 백엔드 페이지에 요청했는데 계속 id값을 못불러와서 전달이 안되어 백엔드에서 호출이 안되었다.

 

문제 코드

export default function BoardDetail() {
  const [data, setData] = useState();
  const [isLoading, setIsLoading] = useState(false);
  
  const router = useRouter();
  const { boardId } = router.query;

  useEffect(() => {
    (async () => {
      setIsLoading(true);
      try {
        const board = await getBoardById(boardId);
        setData(board);
      } catch (error) {
        console.log(error);
      } finally {
        setIsLoading(false);
      }
    })();
  }, []);

  return (
    <>
      <BoardDetailUI data={data} isLoading={isLoading} />
    </>
  );
}

알고보니 useEffect의 배열에 값에 boardId값을 전달안해주어서 발생한 것으로, boardId 값을 넣어주니 렌더링이 된다는 것을 확인했다.

 

수정된 코드

export default function BoardDetail() {
  const [data, setData] = useState();
  const [isLoading, setIsLoading] = useState(false);

  const router = useRouter();
  const { boardId } = router.query;

  useEffect(() => {
    if (!boardId) {
      return;
    }
    (async () => {
      setIsLoading(true);
      try {
        const board = await getBoardById(boardId);
        setData(board);
      } catch (error) {
        console.log(error);
      } finally {
        setIsLoading(false);
      }
    })();
  }, [boardId]); // 추가

  return (
    <>
      <BoardDetailUI data={data} isLoading={isLoading} />
    </>
  );
}

 

참고 URL

https://github.com/vercel/next.js/discussions/11484

터미널에 yarn dev or npm run dev 실행 시 http://localhost:3000 으로 진입이 불가능할 때 해결 방법

 

1) 루트 경로에 .babelrc 파일을 생성 후 다음과 같이 입력

{
  "presets": ["next/babel"],
  "plugins": []
}

2) 루트 경로에 있는 .eslintrc.json 파일을 열어 코드를 아래와 같이 수정

{
  "extends": ["next/babel","next/core-web-vitals"]
}

3) next.config.js에 공백 부분을 밑의 코드 처럼 수정함

const nextConfig = {
    reactStrictMode: true,
    swcMinify: true,
    experimental: {
        appDir: true,
    },
}

+ Recent posts