Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ 4주차 기본/생각 과제 ] 🍁 로그인/회원가입 #4

Open
wants to merge 36 commits into
base: main
Choose a base branch
from

Conversation

doyn511
Copy link
Contributor

@doyn511 doyn511 commented Nov 17, 2023

✨ 구현 기능 명세

🌱 기본 조건

  • .env 파일 사용하기

🧩 기본 과제

[ 로그인 페이지 ]

  1. 로그인
    • 아이디와 비밀번호 입력후 로그인 버튼을 눌렀을시 성공하면 /mypage/:userId 로 넘어갑니다. (여기서 userId는 로그인 성공시 반환 받은 사용자의 id)
  2. 회원가입 이동
    • 회원가입을 누르면 /signup으로 이동합니다.

[ 회원가입 페이지 ]

  1. 중복체크 버튼
    • ID 중복체크를 하지 않은 경우 검정색입니다.
    • ID 중복체크 결과 중복인 경우 빨간색입니다.
    • ID 중복체크 결과 중복이 아닌 경우 초록색입니다.
  2. 회원가입 버튼
    • 다음의 경우에 비활성화 됩니다.
    • ID, 비밀번호, 닉네임 중 비어있는 input이 있는 경우
    • 중복체크를 하지 않은 경우
    • 중복체크의 결과가 중복인 경우
    • 회원가입 성공시 /login 으로 이동합니다.

[ 마이 페이지 ]

  1. 마이 페이지
    • /mypage/:userId 의 userId를 이용해 회원 정보를 조회합니다.
    • 로그아웃 버튼을 누르면 /login으로 이동합니다.

🌠 심화 과제

[ 로그인 페이지 ]

  1. 토스트
    • createPortal을 이용합니다.
    • 로그인 실패시 response의 message를 동적으로 받아 토스트를 띄웁니다.

[ 회원가입 페이지 ]

  1. 비밀번호 확인
    • 회원가입 버튼 활성화를 위해서는 비밀번호와 비밀번호 확인 일치 조건까지 만족해야 합니다.
  2. 중복체크
    • 중복체크 후 ID 값을 변경하면 중복체크가 되지 않은 상태(색은 검정색)로 돌아갑니다.

생각과제

  • API 통신에 대하여

로딩 / 에러 처리를 하는 방법에는 어떤 것들이 있을까?패칭 라이브러리란 무엇이고 어떤 것들이 있을까?패칭 라이브러리를 쓰는 이유는 무엇일까?


💎 PR Point

Router 설정

import { BrowserRouter, Route, Routes } from "react-router-dom";
import MainPage from "./pages/main/MainPage";
import LogIn from "./pages/login/LoginPage";
import SignUp from "./pages/signup/SignupPage";
import MyPage from "./pages/mypage/MyPage";

function Router() {
  return (
    <>
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<MainPage />} />
          <Route path="/login" element={<LogIn />} />
          <Route path="/signup" element={<SignUp />}></Route>
          <Route path="/mypage/:userId" element={<MyPage />}></Route>
        </Routes>
      </BrowserRouter>
    </>
  );
}

export default Router;


SignUp Page

회원가입 창에서 작성된 input값들을 서버로 POST하는 함수

const [newID, setNewID] = useState("");
  const [newPassword, setNewPassword] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");
  const [newNickname, setNewNickname] = useState("");

  const navigate = useNavigate();

  const handlePost = async () => {
    try {
      await axios.post(`${import.meta.env.VITE_APP_BASE_URL}/api/v1/members`, {
        username: newID,
        password: newPassword,
        nickname: newNickname,
      });

      navigate("/login");
    } catch (error) {
      console.log(error);
    }
  };

input의 요소가 모두 작성되었는지, ID의 중복체크가 이루어졌는지, 비밀번호가 일치하는지 확인하는 함수

  const checkInputFilled = () => {
    return (
      newID !== "" &&
      newPassword !== "" &&
      confirmPassword !== "" &&
      newPassword === confirmPassword &&
      newNickname !== "" &&
      isIdValid
    );
  };

  useEffect(() => {
    setIsActive(checkInputFilled());
  }, [newID, newPassword, confirmPassword, newNickname, isIdValid]);
  • useEffect를 사용해 해당 deps값이 변경되었을 때만 실행되도록 해두었습니다!

중복 체크 함수

  const checkIsValid = async () => {
    try {
      const response = await axios.get(
        `${import.meta.env.VITE_APP_BASE_URL}/api/v1/members/check`,
        {
          params: {
            username: value,
          },
        }
      );

      if (response.data.isExist) {
        setIsIdValid(false);
      } else if (!response.data.isExist) {
        setIsIdValid(true);
      }
    } catch (err) {
      console.log(err);
    }
  };

  useEffect(() => {
    setIsIdValid(null);
  }, [value]);
  • POST가 아닌 GET을 이용해 isExist의 값을 받아온 뒤, 처리했습니다!

Login Page

로그인 페이지에서 작성된 id와 비밀번호를 POST하는 함수

const navigate = useNavigate();

  const handlePost = async () => {
    try {
      const response = await axios.post(
        `${import.meta.env.VITE_APP_BASE_URL}/api/v1/members/sign-in`,
        {
          username: inputUsername,
          password: inputPW,
        }
      );
      console.log("login 성공", response);
      navigate(`/mypage/${response.data.id}`);
    } catch (error) {
      console.log(error);
    }
  };

MyPage

userId에 해당하는 usernamenickname을 서버로부터 GET으로 받아오는 함수

const { userId } = useParams();

  const navigate = useNavigate();

  const handleGet = async () => {
    try {
      const response = await axios.get(
        `${import.meta.env.VITE_APP_BASE_URL}/api/v1/members/${userId}`,
        {
          userId: userId,
        }
      );
      const { data } = response;
      setGetUsername(data.username);
      setGetNickname(data.nickname);
    } catch {
      console.log("error");
    }
  };

  useEffect(() => {
    handleGet();
  }, []);
  • 사실 처음에는 mypage의 주소를 userId가 아닌 username으로 해서 제대로 된 Promise 객체를 받아오지 못했답니다....

🥺 소요 시간, 어려웠던 점

  • 2days
  • 뷰를 짜는데는 전보다 훨씬 익숙해져서 오랜 시간이 걸리진 않았지만, api 연결과 서버와의 통신을 처음 해보는거라 처음에는 감을 잡지 못해 오랜 시간이 걸렸어요.
  • 아직도 어떤 컴포넌트를 어떤 폴더에 넣어야 하는지, 폴더 구조를 짜는데 확신이 없어 어려움을 겪는거 같아요.
  • 처음에 Wrapper 부분을 form 태그로 구성했더니 제출 시 무조건 렌더링이 되어 데이터를 확인할 수 없어 요것도 시간이 좀 걸렸던 것 같아요.
  • 기본 주소인 "/'에 대한 조건이 없어 어떻게 "/login"부터 시작하나 고민을 했었는데, 임의로 home 페이지를 만들어 해결했습니다..!

🌈 구현 결과물

https://six-puppy-7ec.notion.site/4-3dcbe96cff8b42fc8787e655e7434c6e?pvs=4

Copy link

@hae2ni hae2ni left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

귣~!
도윤이 너무너무 수고했어! 과제 제출하는 당일까지 진짜 아침부터 얼마나 고생했는지 나는 다 알지!!! 그래도 도움이 된 것 같아서 너무 다행이다! 너무 열심히 한 흔적이 보이고 고민한 부분이 보여서 너무너무 좋아!!! 진짜 쑥쑥 성장할 도유니를 기대할게 라뷰~!

@@ -0,0 +1,29 @@
export const TypeList = [
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

완전 깔끔하네요!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상수 이름은 주로 대문자 + 스네이크 표기법을 이용합니다!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그러면 TYPE_LIST가 되겠네요!

},
];

export const Profile =
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 마찬가지로 PROFILE로 바꿔줄 수 있겠네요!

@@ -0,0 +1,34 @@
import styled from "styled-components";

const PerInput = ({ title, type, placeholder, value, onChange, disabled }) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 받아오는 인자가 많을 땐 하나의 인자로 받아서 그 부분을 구조 분해 할당으로 바꿔주는 건 어때요?

Suggested change
const PerInput = ({ title, type, placeholder, value, onChange, disabled }) => {
const PerInput = ({ title, type, placeholder, value, onChange, disabled }) => {
CONST PerInput = (props) => {
const {title, type, placeholder, value, onChange, disabled}= props

}
};

const GoToSignUp = () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그냥 한번에 이동하는 거니까 Link 컴포넌트를 사용하는 게 더 좋을 것 같아요!
useNavigate랑 Link랑 차이점을 알고 있으면 더 편하겠죠?
Link vs useNavigate


const navigate = useNavigate();

const handlePost = async () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나중에 이런 부분 함수 따로 빼주는 것도 너무 좋겠다!


export default IdCheckBtn;

const Button = styled.button`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그리고 스타일컴포넌트도 컴포넌트니까 이 컴포넌트가 무엇을 하는 친구인지 바로 알수 있는 이름을 지어야 나중에 헷갈리지 않겠죵?


const SignUp = () => {
// 회원가입 버튼 활성화
const [isActive, setIsActive] = useState(false);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

관리해야 할 state가 많다면? reducer를 사용해보는 것도 나쁘진 않아!


const navigate = useNavigate();

const handlePost = async () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

히히 귣!!

};

useEffect(() => {
setIsActive(checkInputFilled());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 이거 너무 깔끔한데? 나도 리팩토링 때 참고해야겠다

@@ -0,0 +1,33 @@
import styled from "styled-components";
export const Wrapper = styled.div`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

많이 쓰는 style요소 빼오는 거 너무 칭찬해!

Copy link
Member

@binllionaire binllionaire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

과제하느라 고생했어요 !!! 🖤 나도 처음 api통신 할 때는 뭐가 뭔지 모르겠고 엄청 헤맸었어,,
그래도 몇 번 해보면 딱 감이 잡히더라고 !
상수화도 되어있고 점점 잘해지는게 보인다 보여!!!! 👀

export default LogInPage;

const SignUpBtn = styled(Btn)`
background-color: #fffffff;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요기 f가 7개 들어갔다!!!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혜인언니 말대로 theme 한번 써보는 거 추천!!!!

Comment on lines +28 to +30
} catch {
console.log("error");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 하면 무슨 error인지 더 알기 쉽겠징?!

Suggested change
} catch {
console.log("error");
}
catch (error) {
console.error(error);
}

{isLoginPage && (
<InLoginPageBtn onClick={onClick}>회원가입</InLoginPageBtn>
)}
{isSignUpPage && !isActive && (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동의합니당!!

Comment on lines +12 to +14

const { userId } = useParams();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우왁 useParams라는게 있구나 ㅠㅠ 나는 path가져와서 직접 쪼개줬는데 꿀팁알아갑니다용

Comment on lines +60 to +69
const InputWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`;

const BtnWrapper = styled(InputWrapper)`
justify-content: end;
gap: 1rem;
width: 75%;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InputWrapper의 속성도 가지면서 BtnWrapper만의 속성을 주는게 맞을까요?

Comment on lines +41 to +50
const checkInputFilled = () => {
return (
newID !== "" &&
newPassword !== "" &&
confirmPassword !== "" &&
newPassword === confirmPassword &&
newNickname !== "" &&
isIdValid
);
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공백도 안되게 해놓은건가?!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants