-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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> |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 같은 이미지 파일도 제거 가능합니다!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
맞습니다. 심지어 index.css파일은 main.tsx에서 불러오면서 사용까지 되고있는 것 같네요. 이러면 의도치 않은 디자인들이 들어갈 수 있습니다!
There was a problem hiding this comment.
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` |
There was a problem hiding this comment.
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가 이름이 똑같아서 살짝 헷갈릴 수도 있을 것 같아요..!
(정말 제 개인적인 생각입니다!!)
저도 스타일 컴포넌트 관리할 때 파일별로 나누는 것도 고려해봐야겠네요 😄
좋은 방법하나 얻어갑니다~!!
There was a problem hiding this comment.
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}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p5: 오 저는 그냥 메시지 바로 띄웠는데 이렇게 '로그인 실패' 두는 것도 사용자 입장에서 알아보기 더 편할 것 같다는 생각이 드네요 👍 배워갑니다~!!
There was a problem hiding this comment.
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", { |
There was a problem hiding this comment.
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를 비교하고 사용법도 나와있으니 참고하셔도 좋을 것 같아서 첨부합니다~:)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p1: 그리고 API 통신에 사용되는 경로들은 .env 파일로 빼주세요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 또한 세미나때도 알려주셨듯이, axios가 쉽고 간결할 뿐 아니라, CSRF 보호등 보안적인 측면에서도 장점이 있답니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아직 axios가 익숙치 않아 fetch를 사용해보았는데 axios가 이런 장점이 있는 줄 몰랐습니다~
There was a problem hiding this 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로 변경한다면 더 쉽게 알 수 있는 것 처럼요!
사소한 부분이라도 조금 더 신경쓰고, 코드를 작성할 때 왜 이 코드를 적어야하는지 생각하는 습관을 많이 기르면 좋겠습니다! 제가 리뷰 남겨놓은 부분도 생각해서 답글 달아주세요!
합세 파이팅 🥧
<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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4: 인라인 스타일은 지양하는 게 좋을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옙 감사합니다.
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; | ||
} | ||
`; |
There was a problem hiding this comment.
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는 커스텀해놓고 사용하지 않았는지 알 수 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
커스텀 해놓고 놓쳐 제가 사용하지 않은 것 같습니다..!!
export const FromSmall = styled.small` | ||
color:blue; | ||
|
||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4: 변수 네이밍에 오탈자가 있는 것 같아요! 그리고 small 태그는 처음보네요! 새로운 태그 알아갑니다 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옙 감사합니다.
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 }), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p5: 저도 axios를 사용해서 리팩토링 한 번 하는 걸 추천드려요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
옙~~
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>(""); |
There was a problem hiding this comment.
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", { |
There was a problem hiding this comment.
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!, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p5: 여기서 느낌표는 왜 작성했는지 알 수 있을까요?
There was a problem hiding this comment.
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" }}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4: 여기도 인라인 스타일 지양!!
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p4: 이 className은 어디서 사용되고 있나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사용안되지만 혹시 CSS다룰때 사용될까봐 습관적으로 넣은 것 같습니다.
const locationHeader = response.headers.get("Location"); | ||
const memberId = locationHeader | ||
? locationHeader.split("/").pop() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p5: 이 부분은 왜 이렇게 작성하셨는지 설명해주실 수 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 궁금합니다! /로 스플릿해주는 이유가 뭘까요...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
locationHeader.split("/")를 통해 URL을 "/" 기준으로 분리하고 그 분리한 결과값을 memberID로 저장하기 위해 스플릿을 했습니다~~
There was a problem hiding this 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"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ko
드리뷰 시작하겠습니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p5: .env파일을 쓸 때 설정하는 파일로 알고있는데, env 파일을 안쓰시는 것 같은데 이 파일이 있는 이유가 있을까요?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p5: React.FC 는 처음보는데 혹시 어떤 코드인가요?
There was a problem hiding this comment.
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", { |
There was a problem hiding this comment.
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" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p5: alt태그 작성해주신거 좋네요! 다만 alt 속성안에 image라는 단어를 다시 써주지 않아도 되는 것으로 알고있습니다!
} else { | ||
throw new Error( | ||
`Failed to fetch: ${response.status} ${response.statusText}` | ||
); | ||
} | ||
} catch (err) { | ||
setError("에러발생"); | ||
console.error("error:", err); // 브라우저 콘솔에 오류 로깅 | ||
} |
There was a problem hiding this comment.
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는 어떤 차이가 있는건가요?
There was a problem hiding this comment.
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 블록 내에서 발생할 수 있는 예상치 못한 에러를 포괄적으로 처리하기 위해 사용됩니다.
const locationHeader = response.headers.get("Location"); | ||
const memberId = locationHeader | ||
? locationHeader.split("/").pop() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저도 궁금합니다! /로 스플릿해주는 이유가 뭘까요...?
@media (prefers-color-scheme: light) { | ||
:root { | ||
color: #213547; | ||
background-color: #ffffff; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요건 무슨 코드인가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
개발에서 사용되는 CSS 조건부 쿼리이며 테마 설정이 라이트 모드일 때 웹 페이지의 기본 글자색과 배경색을 지정하는 용도로 사용됩니다.
✨ 구현 기능 명세
🧩 기본 과제
메인 페이지
로그인 페이지
회원가입 페이지
마이페이지
🔥 심화 과제
메인페이지
로그인 페이지
회원가입 페이지
input이 비어있는 상태로 api연결 시도했을시
해당 input 테두리 색상 변경
input에 focus 맞추기
api요청 금지
전화번호 양식 정규표현식으로 자동입력되도록 설정 (숫자만 입력해도 "-"가 붙도록)
비밀번호 검증 유틸 함수 구현 (검증 통과되지 않을시 api요청 금지)
공유과제
링크 첨부(팀 블로그 링크) : https://forweber.palms.blog/nowsopt-lighthouse-minjune
📌 내가 새로 알게 된 부분
원격 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) 등을 설정해줄 수 있다는 것을 배웠습니다.
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일..
🌈 구현 결과물