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 41 commits into
base: main
Choose a base branch
from

Conversation

imddoy
Copy link
Contributor

@imddoy imddoy commented May 10, 2024

✨ 구현 기능 명세

🧩 기본 과제

  1. 메인 페이지

    • 메인 이미지 or 비디오 넣기
    • 내정보페이지와 회원가입 페이지로 이동할 수 있는 버튼 구현
  2. 로그인 페이지

    • 아이디와 비밀번호를 입력할 수 있는 input구현
    • Login page 이미지 넣기
    • 로그인 버튼(기능)과 회원가입 페이지 이동 버튼 구현
    • 로그인 실패시 해당 에러메세지를 alert로 띄어주기
    • useParam 활용해서 id값 보유하고 있기.
  3. 회원가입 페이지

    • 아이디, 패스워드, 닉네임, 핸드폰 번호를 입력 받는 페이지 구현
    • 회원가입 버튼 클릭시 post api 통신을 진행하고 성공시 회원가입이 완료되었다는 메시지를 보여주는 alert 띄워준 후, 로그인 메인페이지로 이동
    • 아이디 중복, 비밀번호 형식 오류, 전화번호 형식 오류 등 모든 에러 alert로 메세지 보여주기
    • 비밀번호와 전화번호 형식은 input 아래에 보여주기
  4. 마이페이지

    • get 메소드를 사용해 사용자 정보를 가져오기
    • 서버에서 받아온 ID, 닉네임, 전화번호 데이터를 렌더링
    • 비밀번호 변경 토글을 사용해 비밀번호 변경 폼을 on/off할 수 있도록 구현
    • 기존 비밀번호 입력, 새로운 비밀번호 입력, 새로운 비밀번호 확인 input 구현
    • input이 비어있을 경우 api 작동되지 않도록 구현
    • 에러 발생시 api error객체 안 error message를 사용해 alert 띄우기
    • 홈 이동 버튼 구현

🔥 심화 과제

  1. 메인페이지

    • 비디오에 여러 기능을 적용
  2. 로그인 페이지

    • input이 비어있을 경우 api요청 보내지 않고 아래 error message를 띄워주기
  3. 회원가입 페이지
    input이 비어있는 상태로 api연결 시도했을시

    • 해당 input 테두리 색상 변경

    • input에 focus 맞추기

    • api요청 금지

    • 전화번호 양식 정규표현식으로 자동입력되도록 설정 (숫자만 입력해도 "-"가 붙도록)

    • 비밀번호 검증 유틸 함수 구현 (검증 통과되지 않을시 api요청 금지)


📌 내가 새로 알게 된 부분

  • 정규표현식을 잘 몰라서 계속 검색해보면서 새롭게 알게 되었습니다!
  • patch라는 메서드를 처음 사용해봤어요 부분적으로 수정할때 사용한다고 하네요... 신기하다..~

💎 구현과정에서의 고민과정(어려웠던 부분) 공유!

  • 타입스크립트로 합동세미나 개발을 하게 되어서 이번 과제에 타입스크립트를 냅다 써봤는데, 타입 지정이 너무 어렵네요... 그래서 그냥 돌아가는 것을 중점으로 구현하긴 했습니다....
  • 특히 이벤트 관련해서 타입을 지정하는게 제일 어려웠던 것 같아요
  • 타입스크립트를 쓰니까 전에 하던거랑은 스타일컴포넌트 사용하는게 조금 달라져서 계속 돌아가는지 안돌아가는지 확인하면서, 검색하면서 무지성으로 코딩하게 되어서 조금 아쉽습니다...
  • 항상 스타일린트가 잘 안먹혀서, 프리티어랑 eslint만 사용해서 했는데 이번엔 스타일린트를 사용해보자! 해서 진짜 4시간은 붙잡고 늘어진 것 같습니다. 그래도 적용이 되니까 항상 css 속성 순서 뒤죽박죽이었는데 자동으로 정렬되어서 훨씬 좋은 것 같네요!

🥺 소요 시간

  • 15h

🌈 구현 결과물

https://imdevdoy.notion.site/ecf9dc6794f349c7876c5e152f4279bd?pvs=4

카드 클릭 -> 오픈
매칭 성공 -> 오픈 고정
매칭 실패 -> 오픈 취소
카드 매칭 -> 스코어 +1
headercontroller -> header 로 짧게 변경
null은 falsy한 값이므로 똑같게 사용됨
 null인 경우와 다른 falsy한 값(0 등)일 경우가 다른 경우로 분기되어야 하는 상황은 제외
코드를 봤을때 이해하기 쉽도록
5로 하드코딩하는 것이 아닌 상수에서 값을 가져오기
전화번호 -은 실행 안됨
@imddoy imddoy self-assigned this May 10, 2024
Comment on lines +7 to +25
const router = createBrowserRouter([
{
path: '/',
element: <Login />,
},
{
path: '/signup',
element: <Signup />,
},
{
path: '/main/:memberId',
element: <Main />,
},
{
path: '/mypage/:memberId',
element: <Mypage />,
},
]);

Copy link
Member

Choose a reason for hiding this comment

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

path를 좀 더 안전하게 사용하는 방법도 있다는 것 아시나요!

route.ts를 하나 만든 다음,

const STATIC = {
  HOME: "/",
  LOGIN: "/login",
  SIGNUP: "/signup",
};

const DYNAMIC = {
  MYPAGE: "/mypage/:id",
};

export const PATH = {
  ...STATIC,
  ...DYNAMIC,
} as const;

이런식으로 작성해보는 것도 좋을것같아요! 저희 합세 조 라우터는 위와같이 세팅할 예정이라 ㅎㅎ

return;
}
try {
const response = await axios.post('http://34.64.233.12:8080/member/login', {
Copy link
Member

Choose a reason for hiding this comment

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

이렇게 baseUrl같은 경우에는 환경변수로 빼둬봅시다 ㅎㅎ

Comment on lines +33 to +37
if (response.status === 200) {
const memberId = response.headers.location;
alert('로그인이 완료되었습니다.');
navigate(`/main/${memberId}`);
}
Copy link
Member

Choose a reason for hiding this comment

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

음 어차피 성공하면 200번이 뜰텐데 if문으로 작성해주신 이유가 있을까용?

Comment on lines +3 to +9
interface InfoTextProps {
text: string;
}

export default function InfoText({ text }: InfoTextProps) {
return <I.Text>{text}</I.Text>;
}
Copy link
Member

Choose a reason for hiding this comment

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

요롷게 text를 prop으로 주는 것도 좋지만, 추후 확장성 대비해서 children을 적극활용해봐요!

관련 고민 제 아티클 남겨둡니다! 심화스터디 아티클이에요!
https://popeyes9304.tistory.com/

if (response.status === 200) {
const memberId = response.headers.location;
alert('로그인이 완료되었습니다.');
navigate(`/main/${memberId}`);
Copy link
Member

Choose a reason for hiding this comment

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

사실 매번

const navigate = useNavigate()
//이렇게 매번 사용하기 귀찮잖아요??

useEasyNavigate 커스텀 훅으로 사용해도 좋을 것 같아요!

제 과제 코드 중에 useEasyNavigate 참고해보세용~

Comment on lines +10 to +16
function goMypage() {
navigate(`/mypage/${memberId}`);
}

function goSignup() {
navigate('/signup');
}
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,13 @@
import * as B from './ButtonStyle';
interface ButtonProps {
children: any;
Copy link
Member

Choose a reason for hiding this comment

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

children의 type을 바꿔봅시다 ㅎㅎ
구글링하면 바로 나올거에요!

Comment on lines +2 to +6
interface ButtonProps {
children: any;
onClick: () => void;
type?: 'button' | 'submit' | 'reset';
}
Copy link
Member

Choose a reason for hiding this comment

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

또한 button 같은 경우에는

interface ButtonProps extends extends React.ButtonHTMLAttributes<HTMLButtonElement> {

}

이런식으로 interface를 상속 받는다면 따로 onClick을 정의해줄 필요가 없어집니다!

Comment on lines +29 to +31
if (response.status === 200) {
setUserData(response.data.data);
}
Copy link
Member

Choose a reason for hiding this comment

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

여기도 if문의 의도가 궁금합니다!

<Title text="마이 페이지" />
<div>
<Label text="ID" />
<Input type="text" id="id" value={userData.authenticationId} readOnly />
Copy link
Member

Choose a reason for hiding this comment

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

오 readOnly 꼼꼼하네요 좋습니다 👍👍👍

Comment on lines +73 to +75
const phoneAutoHyphen = (phone: string) => {
return phone.replace(/[^0-9]/g, '').replace(/^(\d{3})(\d{4})(\d{4})$/, '$1-$2-$3');
};
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 +78 to +101
function validatePassword(password: any) {
const minLength = 8;
const hasNumbers = /\d/;
const hasEnglish = /[a-zA-Z]/;
const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/;

if (password.length < minLength) {
alert('비밀번호는 최소 8자 이상이어야 합니다.');
return false;
}
if (!hasNumbers.test(password)) {
alert('비밀번호에는 최소 한 개의 숫자가 포함되어야 합니다.');
return false;
}
if (!hasEnglish.test(password)) {
alert('비밀번호에는 최소 한 개의 영어 알파벳이 포함되어야 합니다.');
return false;
}
if (!hasSpecial.test(password)) {
alert('비밀번호에는 최소 한 개의 특수 문자가 포함되어야 합니다.');
return false;
}
return true;
}
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 +63 to +90
<P.Form isOpen={isOpen}>
<div>
<Label text="기존 비밀번호" />
<Input
type="text"
id="current-password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
/>
</div>
<div>
<Label text="새 비밀번호" />
<Input
type="text"
id="new-password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
</div>
<div>
<Label text="비밀번호 확인" />
<Input
type="text"
id="confirm-password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
</div>
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

@ExceptAnyone ExceptAnyone left a comment

Choose a reason for hiding this comment

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

전반적으로 관심사 분리를 잘 해주셨는데,
스타일 코드를 분리하신 것처럼, 커스텀훅으로 기능을 분리하는 연습을 해보는 것도 좋을 것 같아요!

기능을 아예 전담으로 위임해버리면, 다른 쪽에서도 사용 가능하니까!
또한 api 관련된 코드도 따로 모아놓으면 더 컴포넌트가 깔끔해지겠죠?
한 번 고민해보시길 ㅎㅎ

Copy link

@Kjiw0n Kjiw0n left a comment

Choose a reason for hiding this comment

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

컴포넌트 별로 파일 세심하게 구분하신 부분, 에러처리 자세하고 깔끔하게 처리하신 부분 많이 배워갑니다 👍 👍

4주차 과제 수고하셨습니다!

Comment on lines +23 to +26
if (!currentPassword || !newPassword || !confirmPassword) {
alert('비밀번호를 입력해주세요.');
return;
}
Copy link

Choose a reason for hiding this comment

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

newPassword와 confirmPassword 일치여부 비교하는 로직도 들어가면 좋을 것 같아요!


try {
const response = await axios.patch(
'http://34.64.233.12:8080/member/password',
Copy link

Choose a reason for hiding this comment

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

BASE_URL의 경우는 환경변수 등으로 빼두면 좋을 것 같습니다 ㅎㅎ

Comment on lines +38 to +45
} catch (error: any) {
if (error.response) {
alert(error.response.data.message);
} else if (error.request) {
alert('응답을 받지 못했습니다.');
} else {
alert('요청 중 오류가 발생했습니다.');
}
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 +43 to +45
const goHome = () => {
navigate(`/main/${memberId}`);
};
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.

이렇게 하나하나 분리하신 부분 세심하네요 👍 배워갑니다!

Copy link
Contributor

@ljh0608 ljh0608 left a comment

Choose a reason for hiding this comment

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

커밋컨벤션에서 remove 는 보통 파일이나 폴더를 삭제할 때 사용하고
chore는 기타 변경사항으로 많이 사용합니다, 빌드 관련된 커밋이나, assets관련된 커밋으로요!
또한 스타일 분리 (C dot네이밍 사용) 은 기능은 변경되지 않았지만 코드 가독성을 위해 개선한 것이니 refactor 을 사용해도 좋을 것 같습니다!
주석 지우는건 chore로 사용하는 것이 더 의미있지 않을가요??

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

Successfully merging this pull request may close these issues.

4 participants