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주차 기본 / 공유 과제 ] 로그인 회원가입 기능 구현 #5

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

Conversation

202010927choiminjune
Copy link
Contributor

@202010927choiminjune 202010927choiminjune 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요청 금지)

공유과제

  • prettier, eslint, styleLint에 대해
  • lighthouse에 대해

링크 첨부(팀 블로그 링크) : https://forweber.palms.blog/nowsopt-lighthouse-minjune


📌 내가 새로 알게 된 부분

  • ~ 부분 이렇게 구현했어요, 피드백 부탁해요!
    ▶️ GET 호출
    원격 API에 있는 데이터를 가져올 때 쓰이는 GET방식의 HTTP 통신입니다.
    fetch(url, options) .then((response) => console.log("response:", response)) .catch((error) => console.log("error:", error));

첫번째 인자로 API의URL, 두번째 인자로 옵션 객체를 받고 옵션 객체에는 HTTP 방식(method), HTTP 요청 헤더(headers), (body) 등을 설정해줄 수 있다는 것을 배웠습니다.

▶️ POST 호출
fetch("URL", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ title: "Test", body: "Testing!", userId: 1, }), }) .then((response) => response.json())
Post의 경우 method 옵션을 POST로 지정해주고, headers 옵션을 통해 JSON 포멧을 사용한다고 알려줘야 하며, 요청 전문을 JSON 포멧으로 직렬화하는 패턴임을 알게 되었습니다.

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

  • ~ 부분이 잘 구현한건지 잘 모르겠어요!

  • ~부분 다른 방법이 있는지 궁금해요!
    ` const handleLogin = async (event: FormEvent) => {
    event.preventDefault();

    try {
    const response = await fetch("http://34.64.233.12:8080/member/login", {
    method: "POST",
    headers: {
    "Content-Type": "application/json",
    },
    body: JSON.stringify({ authenticationId: userId, password: password }),
    });
    `
    맨처음 api명세서를 유심히 안 읽고 location header에 저장하지 않아 코드의 에러가 계속 생겨났습니다.
    그리고 저는 fetch를 사용하여 문자열로 바꾸었는데 axios로도 구현이 가능하다고 하더라구여?! 혹시 방법 알려주시면 감사하겠습니다.

`import { useParams } from 'react-router-dom';

function UserPage() {
const { id } = useParams();
return (


User {id}
{/* 사용자 정보를 동적으로 렌더링 */}

);
}
;`

useParams를 이용하여 id값을 저장하는 방법이 많이 헷갈렸지만 위의 관련 벨로그 로직을 보고 아래와 같이 해결할 수 있었습니다.
const { memberId } = useParams<{ memberId: string }>(); const [userDetails, setUserDetails] = useState<UserDetails>({ id: "", nickname: "", phone: "", });


🥺 소요 시간

  • 6일..

🌈 구현 결과물

Vite-React-TS-Chrome-2024-05-10-19-44-07
Vite-React-TS-Chrome-2024-05-10-19-46-13
Vite-React-TS-Chrome-2024-05-10-19-47-04
Vite-React-TS-Chrome-2024-05-10-19-47-51
Vite-React-TS-Chrome-2024-05-10-19-48-39
Vite-React-TS-Chrome-2024-05-10-19-49-15
Vite-React-TS-Chrome-2024-05-10-20-00-18
Vite-React-TS-Chrome-2024-05-10-20-04-26
Vite-React-TS-Chrome-2024-05-10-20-08-23

@202010927choiminjune 202010927choiminjune self-assigned this May 10, 2024
@202010927choiminjune 202010927choiminjune changed the title Week4 [ 4주차 / 기본 / 공유 과제 ] 로그인 회원가입 기능 구현 May 10, 2024
@202010927choiminjune 202010927choiminjune changed the title [ 4주차 / 기본 / 공유 과제 ] 로그인 회원가입 기능 구현 [ 4주차 기본 / 공유 과제 ] 로그인 회원가입 기능 구현 May 10, 2024
Copy link

@Bowoon1216 Bowoon1216 left a comment

Choose a reason for hiding this comment

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

저번 과제 하느라 넘 수고하셨어요!!
데이터 전송 다루는거 거의 처음이라고 하셨던 것 같은데 잘 하셨네요!👍
이번 합세 때도 기대하겠습니다 😄

<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 + TS</title>

Choose a reason for hiding this comment

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

p5: 여기 타이틀을 Vite + React + TS로 그대로 두기보단, 아래처럼 네이밍해줘도 좋을 것 같아요.
(저도 자주 깜빡하는 부분이긴합니다 ㅎㅎ;😄)
<title>민준이의 로그인</title>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다~

@@ -0,0 +1,68 @@
:root {

Choose a reason for hiding this comment

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

p4: 이 index.css나 App.css 처럼 기본세팅에 존재하지만 사용하지 않는 파일들은 지워줘도 좋을 것 같아요!
쓰지 않는데 괜히 남아있으면 협업하는 과정에서 혼동을 가져올 수 있다고 생각합니다😄
+react.svg, vite.svg 같은 이미지 파일도 제거 가능합니다!!

Copy link
Member

Choose a reason for hiding this comment

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

맞습니다. 심지어 index.css파일은 main.tsx에서 불러오면서 사용까지 되고있는 것 같네요. 이러면 의도치 않은 디자인들이 들어갈 수 있습니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

@@ -0,0 +1,48 @@
import styled from 'styled-components';

export const Container = styled.div`

Choose a reason for hiding this comment

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

p5: 이렇게 스타일 파일을 컴포넌트 별로 나눠서 관리하는 것도 좋은 방법인 것 같네요! 👍
다만 homestyles.ts의 Container와 loginstyles.ts의 Container가 이름이 똑같아서 살짝 헷갈릴 수도 있을 것 같아요..!
(정말 제 개인적인 생각입니다!!)
저도 스타일 컴포넌트 관리할 때 파일별로 나누는 것도 고려해봐야겠네요 😄
좋은 방법하나 얻어갑니다~!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다


if (!response.ok) {
const error = await response.json();
alert(`로그인 실패: ${error.message}`);

Choose a reason for hiding this comment

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

p5: 오 저는 그냥 메시지 바로 띄웠는데 이렇게 '로그인 실패' 두는 것도 사용자 입장에서 알아보기 더 편할 것 같다는 생각이 드네요 👍 배워갑니다~!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

event.preventDefault();

try {
const response = await fetch("http://34.64.233.12:8080/member/login", {

Choose a reason for hiding this comment

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

p5: 데이터 주고받을때 fetch를 사용하셨네요
개인적으로 fetch도 전체적인 흐름을 알 수 있어서 공부하기 좋았지만 axios가 더 쉽게 구현 가능하고 간결한 코드를 작성할 수 있는 것 같더라구요.
https://kindjjee.tistory.com/145
여기 아티클에서 fetch와 axios를 비교하고 사용법도 나와있으니 참고하셔도 좋을 것 같아서 첨부합니다~:)

Copy link
Member

Choose a reason for hiding this comment

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

p1: 그리고 API 통신에 사용되는 경로들은 .env 파일로 빼주세요!

Copy link
Member

Choose a reason for hiding this comment

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

넵 또한 세미나때도 알려주셨듯이, axios가 쉽고 간결할 뿐 아니라, CSRF 보호등 보안적인 측면에서도 장점이 있답니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아직 axios가 익숙치 않아 fetch를 사용해보았는데 axios가 이런 장점이 있는 줄 몰랐습니다~

Copy link
Member

@pepperdad pepperdad left a comment

Choose a reason for hiding this comment

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

이번주 과제도 너무 고생하셨습니다!🥧

네이밍 부분이나 파일명 등을 조금만 더 신경쓰면 좋을 것 같아요!
지금 마이페이지의 이름이 page.tsx 로 되어있는데, MyPage.tsx로 변경한다면 더 쉽게 알 수 있는 것 처럼요!

사소한 부분이라도 조금 더 신경쓰고, 코드를 작성할 때 왜 이 코드를 적어야하는지 생각하는 습관을 많이 기르면 좋겠습니다! 제가 리뷰 남겨놓은 부분도 생각해서 답글 달아주세요!
합세 파이팅 🥧

Comment on lines +11 to +32
<style>
{`

img {
width: 150px;
height: auto;
display: block;
margin: 0 auto 20px;
}

.button {
display: inline-block;
padding: 10px 20px;
margin: 10px;
background-color: #007bff;
color: white;
text-align: center;
border-radius: 5px;
text-decoration: none;
}
`}
</style>
Copy link
Member

Choose a reason for hiding this comment

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

p4: 인라인 스타일은 지양하는 게 좋을 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

Comment on lines +9 to +26
export const StyledImage = styled.img`
width: 150px;
height: auto;
margin-bottom: 20px;
`;

export const StyledLink = styled(RouterLink)`
display: inline-block;
background-color: #76b947;
color: white;
padding: 10px 20px;
margin: 10px;
border-radius: 4px;
text-decoration: none;
&:hover {
background-color: #63983d;
}
`;
Copy link
Member

Choose a reason for hiding this comment

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

p5: styled(RouterLink)이렇게 Link 컴포넌트를 스타일해서 바로 사용할 수도 있군요! 새로 알아갑니다!
혹시 왜 StyledImage,StyledLink는 커스텀해놓고 사용하지 않았는지 알 수 있을까요?

Copy link
Contributor Author

@202010927choiminjune 202010927choiminjune May 16, 2024

Choose a reason for hiding this comment

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

커스텀 해놓고 놓쳐 제가 사용하지 않은 것 같습니다..!!

Comment on lines +22 to +25
export const FromSmall = styled.small`
color:blue;

`;
Copy link
Member

Choose a reason for hiding this comment

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

p4: 변수 네이밍에 오탈자가 있는 것 같아요! 그리고 small 태그는 처음보네요! 새로운 태그 알아갑니다 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

Comment on lines +23 to +30
try {
const response = await fetch("http://34.64.233.12:8080/member/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ authenticationId: userId, 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.

p5: 저도 axios를 사용해서 리팩토링 한 번 하는 걸 추천드려요!

Copy link
Contributor Author

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 +21
const [userDetails, setUserDetails] = useState<UserDetails>({
id: "",
nickname: "",
phone: "",
});
const [password, setPassword] = useState<string>("");
const [newPassword, setNewPassword] = useState<string>("");
const [confirmPassword, setConfirmPassword] = useState<string>("");
const [showPasswordForm, setShowPasswordForm] = useState<boolean>(false);
const [error, setError] = useState<string>("");
Copy link
Member

Choose a reason for hiding this comment

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

p4: 여기 부분 타입 너무 잘 작성해주셨는데, initData가 ""로 이미 존재하므로 타입추론이 되기때문에 제너릭이 없어도 괜찮습니다!

event.preventDefault();

try {
const response = await fetch("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.

p1: 그리고 API 통신에 사용되는 경로들은 .env 파일로 빼주세요!

method: "PATCH",
headers: {
"Content-Type": "application/json",
memberId: memberId!,
Copy link
Member

Choose a reason for hiding this comment

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

p5: 여기서 느낌표는 왜 작성했는지 알 수 있을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

memberId!에 사용된 느낌표는 해당 값이 null 또는 undefined가 아님을 명시적으로 선언할 때 사용되는 것으로 알고 있어 느낌표로 작성했습니다.

<div>
<h1>My Page</h1>
{error && (
<p className="error" style={{ color: "red" }}>
Copy link
Member

Choose a reason for hiding this comment

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

p4: 여기도 인라인 스타일 지양!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

옙 감사합니다.

<p>핸드폰 번호: {userDetails.phone}</p>
<button
onClick={() => setShowPasswordForm(!showPasswordForm)}
className="toggleButton"
Copy link
Member

Choose a reason for hiding this comment

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

p4: 이 className은 어디서 사용되고 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

사용안되지만 혹시 CSS다룰때 사용될까봐 습관적으로 넣은 것 같습니다.

Comment on lines +32 to +34
const locationHeader = response.headers.get("Location");
const memberId = locationHeader
? locationHeader.split("/").pop()
Copy link
Member

Choose a reason for hiding this comment

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

p5: 이 부분은 왜 이렇게 작성하셨는지 설명해주실 수 있을까요?

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
Contributor Author

Choose a reason for hiding this comment

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

locationHeader.split("/")를 통해 URL을 "/" 기준으로 분리하고 그 분리한 결과값을 memberID로 저장하기 위해 스플릿을 했습니다~~

Copy link
Member

@gudusol gudusol 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,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드리뷰 시작하겠습니다

Copy link
Member

Choose a reason for hiding this comment

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

p5: .env파일을 쓸 때 설정하는 파일로 알고있는데, env 파일을 안쓰시는 것 같은데 이 파일이 있는 이유가 있을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

제가 실수로 올린거 같습니다..!(당시 env파일이 무엇인지 잘몰라서..)

@@ -0,0 +1,68 @@
:root {
Copy link
Member

Choose a reason for hiding this comment

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

맞습니다. 심지어 index.css파일은 main.tsx에서 불러오면서 사용까지 되고있는 것 같네요. 이러면 의도치 않은 디자인들이 들어갈 수 있습니다!

import SignUpPage from "./Signuppage";
import Page from "./page";

const App: React.FC = () => {
Copy link
Member

Choose a reason for hiding this comment

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

p5: React.FC 는 처음보는데 혹시 어떤 코드인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

React.FC는 React의 타입스크립트 코드에서 함수형 컴포넌트를 정의할 때 사용되는 코드입니다..!!
<관련자료>
https://shape-coding.tistory.com/entry/TypeScript-ReactFC%EC%97%90-%EC%82%AC%EC%9A%A9%EC%97%90-%EB%8C%80%ED%95%B4-%EC%83%9D%EA%B0%81%ED%95%B4%EB%B3%B4%EA%B8%B0

event.preventDefault();

try {
const response = await fetch("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.

넵 또한 세미나때도 알려주셨듯이, axios가 쉽고 간결할 뿐 아니라, CSRF 보호등 보안적인 측면에서도 장점이 있답니다!

`}
</style>
<div>
<img src="/솝트.jpg" alt="Welcome Image" />
Copy link
Member

Choose a reason for hiding this comment

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

p5: alt태그 작성해주신거 좋네요! 다만 alt 속성안에 image라는 단어를 다시 써주지 않아도 되는 것으로 알고있습니다!

Comment on lines +41 to +49
} else {
throw new Error(
`Failed to fetch: ${response.status} ${response.statusText}`
);
}
} catch (err) {
setError("에러발생");
console.error("error:", err); // 브라우저 콘솔에 오류 로깅
}
Copy link
Member

Choose a reason for hiding this comment

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

p5: try문 안에있는 throw new Error 에서 처리해주는 에러와 catch에서 처리하는 error는 어떤 차이가 있는건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

throw new Error는 특정 조건에서 개발자가 에러를 의도적으로 발생시키기 위해 사용하는 반면, catch 블록은 try 블록 내에서 발생할 수 있는 예상치 못한 에러를 포괄적으로 처리하기 위해 사용됩니다.

Comment on lines +32 to +34
const locationHeader = response.headers.get("Location");
const memberId = locationHeader
? locationHeader.split("/").pop()
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 +57 to +61
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

요건 무슨 코드인가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

개발에서 사용되는 CSS 조건부 쿼리이며 테마 설정이 라이트 모드일 때 웹 페이지의 기본 글자색과 배경색을 지정하는 용도로 사용됩니다.

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.

5 participants