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

Merged
merged 52 commits into from
Jan 5, 2024

Conversation

binllionaire
Copy link
Member

✨ 구현 기능 명세

🌱 기본 조건

  • .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

week3 의 커밋 내역들이 딸려와서 그거 삭제하고 삽질하고 하다가 커밋이 저렇게 하루로 묶여버림 이슈 하.. 😩
저는 이번 과제에서 컴포넌트 재사용성 , 관심사에 따른 코드 분리 , 컴포넌트의 단일 책임 에 신경을 써봤어요

♻️ 로그인 & 회원가입 컴포넌트 재사용

로그인과 회원가입 화면이 유사한 구조를 갖고 있어서 SignForm 컴포넌트로 정의한 후 조건부 렌더링을 통해 구현했어요

const LoginPage = () => {
  return <SignForm type={PAGE.LOGIN} />;
};

const SignUpPage = () => {
  return <SignForm type={PAGE.SIGNUP} />;
};

♻️ Input 컴포넌트 재사용

Input 도 4번 반복 되기 때문에 따로 빼주어서 props를 이용해 구현했습니다

<Input
  pageType={type}
  inputType={INPUT.PWCHECK}
  onInputChange={handleInputChange}
></Input>

🥢 분리할 수 있는 로직은 분리

서버 통신이 필요한 Login , SignUp , DoubleCheck 로 따로 분리했어요!
로직을 분리했기 때문에 수정 시에도 찾기 쉬웠답니당

📋 배열 순환에 every() 사용

array.every() 는 배열을 순환하면서 모든 요소가 조건에 부합하면 true, 아니면 false를 반환합니다
이 메소드를 통해 조건에 부합하는 지 쉽게 확인 할 수 있었어요

const validateInputs = () => {
      const requiredInputs = [INPUT.ID, INPUT.PW, INPUT.PWCHECK, INPUT.NAME];
      const areInputsFilled = requiredInputs.every(
        (inputType) => inputValues[inputType]
      );
      return areInputsFilled;
    };

🧮 대괄호 표기법 (Computed Property Name) 사용

객체의 속성 이름을 동적으로 결정하여 inputValue 업데이트를 간편하게 구현하였어요

  const [inputValues, setInputValues] = useState({
    [INPUT.ID]: "",
    [INPUT.PW]: "",
    [INPUT.PWCHECK]: "",
    [INPUT.NAME]: "",
  });

  const handleChange = (e) => {
    setInputValue(e.target.value);
  };

🥺 소요 시간, 어려웠던 점

  • 11h
    저번 과제를 하면서 리액트와 styled-component에 익숙해져서 구현은 그렇게 오래걸리지 않았지만
    컴포넌트 설계를 고민하는데에 많은 시간을 썼어요

🌈 구현 결과물

회원가입

signup.mov

회원가입 버튼 활성화

check.mov

로그인 성공 & 로그아웃

login.mov

로그인 실패

login_failed.mov

Copy link

@doyn511 doyn511 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,16 @@
<!doctype html>
<html lang="ko">
Copy link

Choose a reason for hiding this comment

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

잊지 않고 야무지게 바꿔줬네.. 역시 우리 갓기🫶

Comment on lines +14 to +20
const Input = ({
pageType,
inputType,
onInputChange,
isAvailable,
setIsAvailable,
}) => {
Copy link

Choose a reason for hiding this comment

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

사실 나도 이렇게 했는데 .. props로 받아와서 구조분해할당으로 주는게 조금 더 깔끔한거 같기도 해... 내 개인적인 생각이야..!

Suggested change
const Input = ({
pageType,
inputType,
onInputChange,
isAvailable,
setIsAvailable,
}) => {
const Input = (props) => {
const { pageType, inputType, onInputChange, isAvailable, setIsAvailable } = props;

Copy link

Choose a reason for hiding this comment

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

마쟈마쟈!!

Comment on lines +1 to +5
export const CHECK = {
NOT_CHECKED: 0,
EXIST: 1,
NOT_EXIST: 2,
};
Copy link

Choose a reason for hiding this comment

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

오..... 이렇게 상수화 시킨거 좋다......

const [inputValue, setInputValue] = useState("");

const handleChange = (e) => {
setIsAvailable && setIsAvailable(CHECK.NOT_CHECKED);
Copy link

Choose a reason for hiding this comment

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

상수화 시키니까 어떤 조건인지 좀 더 쉽게 볼 수 있는거 같아!!!
근데 이 조건은 input에 쓰여진 값이 바뀔 때마다 setIsAvailable (중복체크 관련 state)를 NOT_CHECKED(중복체크 버튼 검정색)로 바꿔주는 그런 ... 조건인가?!


return (
<Sign.InputWrapper>
<img src={iconSrc} />
Copy link

Choose a reason for hiding this comment

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

각 input마다 아이콘 넣은거 너무 귀여워 ........🫶

Copy link

Choose a reason for hiding this comment

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

img 태그에 alt 추가해주기!

useEffect(() => {
const validateInputs = () => {
const requiredInputs = [INPUT.ID, INPUT.PW, INPUT.PWCHECK, INPUT.NAME];
const areInputsFilled = requiredInputs.every(
Copy link

Choose a reason for hiding this comment

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

오...! .every 처음봤어.. forEach랑 뭐가 다른걸까 싶어서 찾아봤는데 every 다음에 오는 함수가 모두 통과하는지 검사하고 boolean값을 반환한다는 점이 다른거 같긴 하다! 하나 새로 알아가!

Comment on lines +4 to +13
const Title = ({ page }) => {
switch (page) {
case PAGE.LOGIN:
return <TitleText>로그인</TitleText>;
case PAGE.SIGNUP:
return <TitleText>회원가입</TitleText>;
case PAGE.MYPAGE:
return <TitleText>마이페이지</TitleText>;
}
};
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.

심화과제까지.. 완벽하다 우리 수빈이..👍

const RequestLogin = async () => {
await axios
.post(
import.meta.env.VITE_BASE_URL + "/api/v1/members/sign-in",
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.

혹시 ${} 이거 안 쓴 이유가 있을까아?

Comment on lines +12 to +27
useEffect(() => {
const fetchData = async () => {
const path = location.pathname;
const idFromURL = path.substring(path.lastIndexOf("/") + 1);
try {
const data = await axios.get(
import.meta.env.VITE_BASE_URL + `/api/v1/members/${idFromURL}`
);
setID(data.data.username);
setName(data.data.nickname);
} catch (error) {
console.error(error);
}
};
fetchData();
}, [location]);
Copy link

Choose a reason for hiding this comment

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

혹시 여기서 26번째 줄에 fetchData()는 왜 넣어줬는지..? 무슨 역할을 하는지 알려줄 수 있을까?! 딱 저부분만 이해를 못했어..

Copy link

@se0jinYoon se0jinYoon left a comment

Choose a reason for hiding this comment

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

갓기야 구경왔다가 디자인에 화들짝 놀라구 간다 ........ 갓기 폼미 ..💗💗

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.

굿굿! 일단 수비니는 상수화에 진짜 능한 거 같아! 나는 그냥 아무 생각없는 것도 뚝딱 상수화하니까 되게 가독성이 좋아지는 것 같아요!! 그리고 commonStyle 따로 빼준 거 너무너무 칭찬해!! 너무 잘 한 거 같아요
로직같은 거는 딱히 내 눈엔 손 댈게 없는 것 같고,, 나중에 fetch 함수들을 utils로 빼서 활용하는 방식으로 리팩토링 해줘도 많이 좋을 것 같다는 생각이 드네요!
진짜 늘 느끼지만 하나하나 정성스럽게 코드 짜주는 것 같아서 내가 다 고마워!

Comment on lines +14 to +20
const Input = ({
pageType,
inputType,
onInputChange,
isAvailable,
setIsAvailable,
}) => {
Copy link

Choose a reason for hiding this comment

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

마쟈마쟈!!


const [inputValue, setInputValue] = useState("");

const handleChange = (e) => {
Copy link

Choose a reason for hiding this comment

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

아예 hanldeChange함수를 빼버렸구나!!?

onInputChange(inputType, e.target.value);
};

useEffect(() => {
Copy link

Choose a reason for hiding this comment

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

너무 깔끔해!! 좋아요좋아요!


return (
<Sign.InputWrapper>
<img src={iconSrc} />
Copy link

Choose a reason for hiding this comment

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

img 태그에 alt 추가해주기!

Copy link

Choose a reason for hiding this comment

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

귀여워,,,,,,,,,,,,,,,,,,,,,,

<ToastWrapper>
<ToastMessage>{message}</ToastMessage>
</ToastWrapper>,
document.body
Copy link

Choose a reason for hiding this comment

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

깔꼼하네용

const RequestLogin = async () => {
await axios
.post(
import.meta.env.VITE_BASE_URL + "/api/v1/members/sign-in",
Copy link

Choose a reason for hiding this comment

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

혹시 ${} 이거 안 쓴 이유가 있을까아?

const [name, setName] = useState("");

useEffect(() => {
const fetchData = async () => {
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.

useEffect 밖으로 뺄 수 있을거야!

const data = await axios.get(
import.meta.env.VITE_BASE_URL + `/api/v1/members/${idFromURL}`
);
setID(data.data.username);
Copy link

Choose a reason for hiding this comment

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

요런거 나중에 점점 많아지면 구조분해할당으로 바꿔줘도 좋습니다!

import.meta.env.VITE_BASE_URL + `/api/v1/members/check?username=${id}`
);
data.data.isExist
? setIsAvailable(CHECK.EXIST)
Copy link

Choose a reason for hiding this comment

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

굿굿 너무 깔끔하다!

Comment on lines +34 to +42
case INPUT.ID:
setIconSrc(loginIcon);
setPlaceholderText("아이디");
break;
case INPUT.PW:
setIconSrc(passwordIcon);
setPlaceholderText("비밀번호");
break;
case INPUT.PWCHECK:

Choose a reason for hiding this comment

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

여기서 위에 INPUT을 상수화 시켜준 이유가 나오는구나!

Comment on lines +11 to +13
const logout = () => {
navigate("/login");
};

Choose a reason for hiding this comment

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

다른 상수로 해서 onClick에 넣어주는거 너무 좋다

@binllionaire binllionaire merged commit b5597cb into main Jan 5, 2024
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.

5 participants