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주차 기본/심화/생각] 로그인/회원가입 #7

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

Conversation

moondda
Copy link
Contributor

@moondda moondda 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

많이 쓰는 style 컴포넌트들은 style.js 에 모으고, 필요한 컴포넌트들은 import 해서 써줬습니다

💪 로그인 성공하면 /mypage/:userId 로 넘어가기

-> navigate 사용!

navigate(`/mypage/${response.data.id}`);

💪 회원가입을 누르면 /signup으로 이동하기

-> Link 사용!

     <Link to="/signup">
        <Button>회원가입</Button>
      </Link>

💪 중복체크 버튼

1. 필요한 변수를 선언하고,

  const [isExist, setIsExist] = useState(false); //중복되는 id가 있는지 api로 데이터를 get해서 확인한다
  const [buttonColor, setButtonColor] = useState(""); 

2. axios.get하고 결과에 따라 buttonColor를 바꾸는 함수 handleIdisExist 선언

const handleIdisExist = () => {
   axios
     .get(import.meta.env.VITE_BASE_URL + "/api/v1/members/check", {
       params: { username: username }, //쿼리스트링으로 보내기
     })
     .then((response) => {
       setIsExist(response.data.isExist);
       if (response.data.isExist) setButtonColor("red"); //만약 존재하면 버튼을 빨간색으로
       else setButtonColor("green"); //존재하지 않으면 초록색으로
     })
     .catch((error) => {
       console.log(error);
     });
 };

3. 함수 handleIdisExist는 중복체크 버튼을 눌렀을 때 동작한다. 함수에서 바꾼 buttonColor를 style에 적용

          <SectionInputExistButton
            onClick={() => {
              handleIdisExist();
            }}
            style={{ backgroundColor: `${buttonColor} ` }}
          >
            중복체크
          </SectionInputExistButton>

4. username이 달라질 때마다 buttonColor를 검정색으로 바꾸기 위해 useEffect 사용

  useEffect(() => {
    setButtonColor("black");
  }, [username]);

💪 /mypage/:userId 의 userId를 이용해 회원 정보를 조회

-> useParam() 사용!

 let { userId } = useParams();
import.meta.env.VITE_BASE_URL + `/api/v1/members/${userId}`;

💪 createPortal 로 토스트 띄우기

  const toastElement = showToast ? (
    <ToastContainer>{toastMessage}</ToastContainer>
  ) : null;
  {createPortal(toastElement, document.body)}

이때 toastMessage는 로그인 실패했을 때의 메시지를 그대로 출력

      .catch((error) => {
        setToastMessage(error.response.data.message);

출력 3초 후에는 사라진다

  useEffect(() => {
    const timer = setTimeout(() => {
      setShowToast(false);
    }, 3000);

    return () => clearTimeout(timer);
  }, [showToast]);

🥺 소요 시간, 어려웠던 점

  • 5h
    -� 중복확인 버튼 색깔을 바꾸는 데에서 시간을 많이 썻다... red/green 은 onClick로 black는 useEffect으로!
  • 쿼리스트링으로 보내야 하는데 자꾸 바디에 넣으려 해서 에러가 떳었다
  • 하지만 전체적으로 수월했다

🌈 구현 결과물

Screen.Recording.2023-11-17.at.5.05.47.PM.mov
Screen.Recording.2023-11-17.at.5.07.34.PM.mov

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
Copy link
Member

Choose a reason for hiding this comment

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

아 이거 언석이한테 리뷰 받았는데 우리 같이 ko로 바꾸는거 같이 습관들여보자 ㅋㅋㅋ 나도 내 습관 고쳐볼게

import { MainBox, Header } from "./style";

export default function MyPage() {
let { 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.

요고는 const 쓰면 안되는 이유가 있나?? 아니면 바뀌어야하는 값이니까 혹시몰라서 let 쓴거야??

Comment on lines +29 to +40
const handleUsername = (e) => {
setUsername(e.target.value);
};
const handlePassword = (e) => {
setPassword(e.target.value);
};
const handlePasswordAgain = (e) => {
setPasswordAgain(e.target.value);
};
const handleNickname = (e) => {
setNickname(e.target.value);
};
Copy link
Member

Choose a reason for hiding this comment

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

나 이 부분 내 pr에 고민이라고 남겼는데, 같이 확장성 있는 함수를 구현하는 방법 찾아보자 ㅎㅎ 아무래도 이런 input창이 백만개라면 하나하나 만들어 줄순 없을테니깐 !!

Comment on lines +45 to +47
username: username,
password: password,
nickname: nickname,
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 +59 to +62
axios
.get(import.meta.env.VITE_BASE_URL + "/api/v1/members/check", {
params: { username: username },
})
Copy link
Member

Choose a reason for hiding this comment

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

이 부분 우리가 아름이 코드 참고해서 axios 인스턴스 만들어주는(?) 방법으로 리팩토링해도 좋을듯해!!

Comment on lines +77 to +88
useEffect(() => {
if (
!isExist &&
password != "" &&
password === passwordAgain &&
nickname != ""
) {
setIsNotFull(false);
} else {
setIsNotFull(true);
}
}, [isExist, password, passwordAgain, nickname]);
Copy link
Member

Choose a reason for hiding this comment

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

나는 버튼 태그 자체에 disabled를 줘서 버튼 활성화 로직을 구현했는데, useEffect로 구현하는 방법도 있었네?? 두 방법이 어떤 장단점이 있는건지 함 찾아봐야겠다..

const fetchUserInfo = async () => {
try {
const response = await axios.get(
import.meta.env.VITE_BASE_URL + `/api/v1/members/${userId}`
Copy link
Member

Choose a reason for hiding this comment

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

얘도 매번 타이핑하기 귀찮을텐데 따로 빼서 관리해보쟈

Copy link
Member

@Arooming Arooming left a comment

Choose a reason for hiding this comment

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

이번 과제도 너무너무 수고했어용 !

다만, 사용하지 않는 App.css, index.css 파일은 삭제해주고 theme, GlobalStyle과 같은 스타일 파일들을 만들어주는건 어떨까요 ?

<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
Copy link
Member

Choose a reason for hiding this comment

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

요기도 프로젝트의 특성에 맞게 바궈줘보기 !

Copy link
Member

Choose a reason for hiding this comment

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

안쓰는 파일 지워주세용

@@ -0,0 +1,19 @@
import { Route, BrowserRouter, Routes } from "react-router-dom";
import "./App.css";
Copy link
Member

Choose a reason for hiding this comment

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

요거 안쓰는거 아닌가용?

<Link to="/signup">
<Button>회원가입</Button>
</Link>
{createPortal(toastElement, document.body)}
Copy link
Member

Choose a reason for hiding this comment

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

모달을 띄워주는 부분도 로그인과는 역할이 다른 부분이라 컴포넌트 분리를 해주는게 좋을 것 같아!
예를 들어 모달을 구현한 컴포넌트 이름을 Modal 이라고 가정한다면,
showModal && <Modal /> 이런 식으로 보다 명확하고 직관적인 코드가 될 수 있겠지 ?!

Comment on lines +65 to +80
<Section>
<SectionTitle>ID</SectionTitle>
<SectionInput
value={username}
onChange={handleUserName}
placeholder="아이디를 입력해주세요"
/>
</Section>
<Section>
<SectionTitle>PASSWORD</SectionTitle>
<SectionInput
value={password}
onChange={handlePassword}
placeholder="비밀번호를 입력해주세요"
/>
</Section>
Copy link
Member

Choose a reason for hiding this comment

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

여기는 사실 거의 똑같은 구조가 반복되고 있는 형태자나!?
중복 코드를 줄일 수 있는 방법을 생각해보면 좋겠다!

나 같은 경우는, 상수파일을 만들어서 id, password 같은 문자열 값을 넣어줬어.
얘네를 map으로 돌려서 각 index 별로 원하는 동작을 수행하게 해줬구 !
다양한 방법이 있겠지만 요런 방법도 있다는거 참고하면 좋을듯 합니당 ~

Comment on lines +79 to +82
!isExist &&
password != "" &&
password === passwordAgain &&
nickname != ""
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
!isExist &&
password != "" &&
password === passwordAgain &&
nickname != ""
!isExist &&
password &&
password === passwordAgain &&
nickname

요렇게도 구현해줄 수 있다 !

Comment on lines +73 to +75
useEffect(() => {
setButtonColor("black");
}, [username]);
Copy link
Member

Choose a reason for hiding this comment

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

위에서 말한 것처럼 조건부 스타일링 적용하는 코드로 하면 요 useEffect도 없앨 수 있겠죵 ?!

onClick={() => {
handleIdisExist();
}}
style={{ backgroundColor: `${buttonColor} ` }}
Copy link
Member

Choose a reason for hiding this comment

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

인라인으로 스타일 지정해주는건 지양해주세용 !!
이유는 지난 pr들에 남겨놨으니 잘 알고 있겠죵 ?!

Comment on lines +111 to +137
<Section>
<SectionTitle>비밀번호</SectionTitle>
<SectionInput
type="text"
value={password}
onChange={handlePassword}
placeholder="비밀번호를 입력해주세요"
/>
</Section>
<Section>
<SectionTitle>비밀번호 확인</SectionTitle>
<SectionInput
type="text"
value={passwordAgain}
onChange={handlePasswordAgain}
placeholder="비밀번호를 다시 한 번 입력해주세요"
/>
</Section>
<Section>
<SectionTitle>NICKNAME</SectionTitle>
<SectionInput
type="text"
value={nickname}
onChange={handleNickname}
placeholder="닉네임을 입력해주세요"
/>
</Section>
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 +165 to +167
border: 1px solid black;
background-color: ${(props) => (props.disabled ? "#ccc" : "black")};
color: ${(props) => (props.disabled ? "#666" : "#fff")};
Copy link
Member

Choose a reason for hiding this comment

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

이번 과제에서도 theme과 GlobalStyle을 따로 정의해주지는 않은 것 같은데 혹시 이유가 있을까용!?

지금은 프로젝트 규모가 작기 때문에 임의의 색을 쓰기도 하고 유지보수에도 큰 리소스가 들지 않아서 딱히 필요성을 못 느낄 수도 있어요.
그치만 앞으로의 협업 과정에서 프로젝트 규모가 커지고 디자인분들께서 전달해주신 색상만을 사용해야 하는데요,
매번 요런 형태로 색상코드를 입력해주는 것은 리소스도 많이 들고 숫자 하나라도 잘못 들어가면 아예 다른 색이 되기 때문에 스타일 파일을 만드는 것은 사실 불가피해요!

작은 프로젝트 + 혼자하는 프로젝트일수록 다양한 부분들을 연습할 수 있다고 생각하고 세미나에서 배운 내용들을 최대한 많이 적용해보는건 어떨까요 ~?

Comment on lines +35 to +38
.post(import.meta.env.VITE_BASE_URL + "/api/v1/members/sign-in", {
username: username,
password: password,
})
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.post(import.meta.env.VITE_BASE_URL + "/api/v1/members/sign-in", {
username: username,
password: password,
})
.post(`${import.meta.env.VITE_BASE_URL}/api/v1/members/sign-in`, {
username,
password,
})

템플릿 리터럴과 단축 프로퍼티 사용하기!

</Sections>

<Button
type="submit"
Copy link
Member

Choose a reason for hiding this comment

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

잊지 않고 type 설정해주기 좋다! 그런데 button의 type중에 submit은

태그에서 form data가 제출되는 버튼임을 명시하기 위함이라서 type="button'이 맞답니당

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