-
Notifications
You must be signed in to change notification settings - Fork 44
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
[우지석] Week14 #448
The head ref may contain hidden characters: "part3-\uC6B0\uC9C0\uC11D-week14"
[우지석] Week14 #448
Changes from all commits
93a0e93
b2e37bd
6f8bbb0
e11e25f
212e864
4dc5dd0
140ab86
58bef45
29d9df9
af73df9
4f0db67
ca08430
6bf4fe8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
// components/EmailInput.tsx | ||
|
||
import React, { useState, useEffect } from "react"; | ||
import classNames from "classnames/bind"; | ||
import styles from "@/styles/signin.module.scss"; | ||
|
||
interface EmailInputProps { | ||
value: string; | ||
onChange: (value: string) => void; | ||
error?: string; | ||
} | ||
|
||
export const emailCheck = (email: string) => { | ||
const emailForm = | ||
/^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i; | ||
return emailForm.test(email); | ||
}; | ||
Comment on lines
+13
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 보통 요런 유틸성 함수는 별도 utils 폴더에 분리해서 관리하곤 합니다! 리액트 컴포넌트 파일에는 보통 리액트 컴포넌트 하나만 default export 하는 식으로 작성합니다 |
||
|
||
export function EmailInput({ value, onChange, error }: EmailInputProps) { | ||
const cx = classNames.bind(styles); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요거는 컴포넌트 렌더링 될 때마다 매번 다시 선언되지 않아도 되는 코드라서 컴포넌트 바깥쪽에 두셔도 돼요 저는 보통 import 문과 컴포넌트 사이에 둡니다 |
||
const [isFocused, setIsFocused] = useState(false); | ||
const [emailErrorText, setEmailErrorText] = useState(""); | ||
|
||
useEffect(() => { | ||
setEmailErrorText(error || ""); | ||
}, [error]); | ||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
const emailInput = e.target.value; | ||
onChange(emailInput); | ||
}; | ||
|
||
const handleBlur = () => { | ||
setIsFocused(false); | ||
if (value) { | ||
if (!emailCheck(value)) { | ||
setEmailErrorText("올바른 이메일 주소가 아닙니다."); | ||
} else { | ||
setEmailErrorText(""); | ||
} | ||
} else { | ||
setEmailErrorText("이메일을 입력해 주세요."); | ||
} | ||
}; | ||
|
||
const handleFocus = () => { | ||
setIsFocused(true); | ||
}; | ||
|
||
const isError = !isFocused && (error || emailErrorText); | ||
|
||
return ( | ||
<div className={cx("input__section")}> | ||
<label className={cx("text")}> | ||
이메일 <br /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 불필요해보이는 br 이네요! |
||
</label> | ||
<input | ||
id="email" | ||
placeholder="이메일을 입력해 주세요." | ||
className={cx("user-input", { "error-input": isError })} | ||
type="email" | ||
name="email" | ||
value={value} | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
onFocus={handleFocus} | ||
/> | ||
<div | ||
id="email-errorText" | ||
className={cx("errortext", { error: !isError })} | ||
> | ||
{emailErrorText} | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import React, { useState } from "react"; | ||
import classNames from "classnames/bind"; | ||
import styles from "@/styles/signin.module.scss"; | ||
|
||
interface PasswordInputProps { | ||
value: string; | ||
onChange: (value: string) => void; | ||
onBlur?: () => void; | ||
error?: string; | ||
id?: string; | ||
} | ||
|
||
export function PasswordInput({ | ||
value, | ||
onChange, | ||
onBlur, | ||
error, | ||
id, | ||
}: PasswordInputProps) { | ||
const cx = classNames.bind(styles); | ||
const [isFocused, setIsFocused] = useState(false); | ||
const [errorText, setErrorText] = useState(""); | ||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
onChange(e.target.value); | ||
}; | ||
|
||
const handleBlur = () => { | ||
setIsFocused(false); | ||
if (onBlur) { | ||
onBlur(); | ||
} | ||
if (!value) { | ||
setErrorText("비밀번호를 입력해 주세요."); | ||
} else { | ||
setErrorText(""); | ||
} | ||
}; | ||
|
||
const handleFocus = () => { | ||
setIsFocused(true); | ||
}; | ||
|
||
const isError = !isFocused && (error || errorText); | ||
|
||
return ( | ||
<div className={cx("input__section", "password-section")}> | ||
<label className={cx("text")}> | ||
비밀번호 <br /> | ||
</label> | ||
<input | ||
id={id || "password"} | ||
placeholder="비밀번호를 입력해 주세요." | ||
className={cx("user-input", { "error-input": isError })} | ||
type="password" | ||
name="password" | ||
value={value} | ||
onChange={handleChange} | ||
onBlur={handleBlur} | ||
onFocus={handleFocus} | ||
/> | ||
<div | ||
id="password-errorText" | ||
className={cx("errortext", { error: !isError })} | ||
> | ||
{error || errorText} | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React, { useState } from "react"; | ||
import { useRouter } from "next/router"; | ||
import axios from "axios"; | ||
import { EmailInput } from "./EmailInput"; | ||
import { PasswordInput } from "./PasswordInput"; | ||
import styles from "@/styles/signin.module.scss"; | ||
import classNames from "classnames/bind"; | ||
|
||
export function SignIn() { | ||
const cx = classNames.bind(styles); | ||
const [email, setEmail] = useState(""); | ||
const [password, setPassword] = useState(""); | ||
const [emailError, setEmailError] = useState(""); | ||
const [passwordError, setPasswordError] = useState(""); | ||
const router = useRouter(); | ||
const handleSubmit = async (e: React.FormEvent) => { | ||
Comment on lines
+10
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다른 파일들처럼 적당히 빈 줄을 추가해주면 더 읽기 편할 것 같아요! |
||
e.preventDefault(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. form event 잘 활용하셨네요 👍🏽 |
||
setEmailError(""); | ||
setPasswordError(""); | ||
const data = { email, password }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. data 도 틀린 건 아닌데 보통 post 요청에 같이 보내는 객체는 body 라고 많이 부릅니다 ! |
||
try { | ||
const response = await axios.post( | ||
"https://bootcamp-api.codeit.kr/api/sign-in", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. api 호스트는 api 사용하는 부분마다 반복될 것 같아서 상수로 분리해서 관리하면 더 좋을 것 같아요! |
||
data | ||
); | ||
if (response.status === 200) { | ||
router.push("/folder"); | ||
} | ||
Comment on lines
+26
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기에 response 내의 토큰을 로컬스토리지에 저장하는 로직 추가해주시면 될 것 같네요! |
||
} catch (error: any) { | ||
if (error.response) { | ||
if (error.response.status === 400) { | ||
setEmailError("이메일을 확인해 주세요."); | ||
setPasswordError("비밀번호를 확인해 주세요."); | ||
} | ||
} | ||
} | ||
}; | ||
return ( | ||
<form className={cx("input")} onSubmit={handleSubmit}> | ||
<EmailInput value={email} onChange={setEmail} error={emailError} /> | ||
<PasswordInput | ||
value={password} | ||
onChange={setPassword} | ||
error={passwordError} | ||
/> | ||
<button type="submit" id="btn" className={cx("signin", "button")}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. id='btn' 은 활용하는 곳이 있나요? |
||
로그인 | ||
</button> | ||
</form> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,66 @@ | ||||||||||
import React, { useState } from "react"; | ||||||||||
import { useRouter } from "next/router"; | ||||||||||
import axios from "axios"; | ||||||||||
import { EmailInput, emailCheck } from "./EmailInput"; | ||||||||||
import { SignUpPassword, isValidPassword } from "./SignUpPassword"; | ||||||||||
import styles from "@/styles/signup.module.scss"; | ||||||||||
import classNames from "classnames/bind"; | ||||||||||
|
||||||||||
export function SignUp() { | ||||||||||
const cx = classNames.bind(styles); | ||||||||||
const [email, setEmail] = useState(""); | ||||||||||
const [password, setPassword] = useState(""); | ||||||||||
const [confirmPassword, setConfirmPassword] = useState(""); | ||||||||||
const [emailError, setEmailError] = useState(""); | ||||||||||
const router = useRouter(); | ||||||||||
|
||||||||||
const signUp = async (e: React.FormEvent) => { | ||||||||||
e.preventDefault(); | ||||||||||
const emailChecked = emailCheck(email); | ||||||||||
const validPassword = isValidPassword(password); | ||||||||||
Comment on lines
+19
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. emailCheck , isValidPassword 둘 다 동일하게 유효성 검증을 하는 함수인데 멘토링 때 말씀드린대로 함수는 동사로 시작하게 작성해보시면 좋을 것 같고, 함수의 리턴값이 boolean 형이니 변수도 boolean 형에 맞게 지어주시면 좋을 것 같고요!
Suggested change
|
||||||||||
const passwordMatch = password === confirmPassword; | ||||||||||
|
||||||||||
if (emailChecked && validPassword && passwordMatch) { | ||||||||||
const checkEmailUrl = "https://bootcamp-api.codeit.kr/api/check-email"; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요거는 submit 시점이 아니라 email 인풋 blur 시점에 확인해야 하는 것 같아요! (요구사항 확인) |
||||||||||
const checkEmailData = { email }; | ||||||||||
try { | ||||||||||
const checkEmailResponse = await axios.post( | ||||||||||
checkEmailUrl, | ||||||||||
checkEmailData | ||||||||||
); | ||||||||||
if (checkEmailResponse.status === 409) { | ||||||||||
setEmailError("이미 사용 중인 이메일입니다."); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
배포 url 이 있어서 확인이 가능했습니다!! 409 에러 났을 때 콘솔에 'hello2' 가 찍히는 걸 보니 47라인이 실행됐다는 건데요 27~30 라인에서 axios 요청하면서 에러가 났기 때문에 31라인 이후로 진행되지 않고 46라인으로 이동한거에요
|
||||||||||
console.log("hello~"); | ||||||||||
return; | ||||||||||
} else { | ||||||||||
setEmailError(""); | ||||||||||
} | ||||||||||
|
||||||||||
const signUpUrl = "https://bootcamp-api.codeit.kr/api/sign-up"; | ||||||||||
const signUpData = { email, password }; | ||||||||||
const signUpResponse = await axios.post(signUpUrl, signUpData); | ||||||||||
if (signUpResponse.status === 200) { | ||||||||||
router.push("/folder"); | ||||||||||
} | ||||||||||
} catch (error) { | ||||||||||
console.log("Error:", error); | ||||||||||
console.log("hello2"); | ||||||||||
} | ||||||||||
} | ||||||||||
}; | ||||||||||
|
||||||||||
return ( | ||||||||||
<form className={cx("signup__form")} onSubmit={signUp}> | ||||||||||
<EmailInput value={email} onChange={setEmail} error={emailError} /> | ||||||||||
<SignUpPassword | ||||||||||
password={password} | ||||||||||
confirmPassword={confirmPassword} | ||||||||||
onPasswordChange={setPassword} | ||||||||||
onConfirmPasswordChange={setConfirmPassword} | ||||||||||
/> | ||||||||||
<button type="submit" className={cx("signup__button")}> | ||||||||||
회원가입 | ||||||||||
</button> | ||||||||||
</form> | ||||||||||
); | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import React, { useState } from "react"; | ||
import { PasswordInput } from "./PasswordInput"; | ||
|
||
interface SignUpPasswordProps { | ||
password: string; | ||
confirmPassword: string; | ||
onBlur?: () => void; | ||
onPasswordChange: (value: string) => void; | ||
onConfirmPasswordChange: (value: string) => void; | ||
} | ||
|
||
export const isValidPassword = (password: string) => { | ||
const passwordForm = /(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/; | ||
return passwordForm.test(password); | ||
}; | ||
|
||
export function SignUpPassword({ | ||
password, | ||
confirmPassword, | ||
onPasswordChange, | ||
onConfirmPasswordChange, | ||
}: SignUpPasswordProps) { | ||
const [passwordError, setPasswordError] = useState(""); | ||
const [confirmPasswordError, setConfirmPasswordError] = useState(""); | ||
|
||
const validatePassword = () => { | ||
const validPassword = isValidPassword(password); | ||
if (!validPassword) { | ||
setPasswordError("비밀번호는 영문,숫자 조합 8자 이상 입력해 주세요."); | ||
} else { | ||
setPasswordError(""); | ||
} | ||
}; | ||
|
||
const checkPassword = () => { | ||
if (password !== confirmPassword) { | ||
setConfirmPasswordError("비밀번호가 일치하지 않아요."); | ||
} else { | ||
setConfirmPasswordError(""); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
<PasswordInput | ||
value={password} | ||
onChange={onPasswordChange} | ||
onBlur={validatePassword} | ||
error={passwordError} | ||
/> | ||
<PasswordInput | ||
value={confirmPassword} | ||
onChange={onConfirmPasswordChange} | ||
onBlur={checkPassword} | ||
error={confirmPasswordError} | ||
id="password-confirm" | ||
/> | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
$(document).ready(function () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 갑분제이쿼리 어디서 나온거죠 ㅋㅋ 급한건 아니니 시간되면 고치시고 그리 중요한 부분은 아니라 냅두셔도 됩니다 |
||
$(".password-section i").on("click", function () { | ||
$("input").toggleClass("active"); | ||
if ($("input").hasClass("active")) { | ||
$(this) | ||
.attr("class", "fa fa-eye-slash fa-lg") | ||
.prev("input") | ||
.attr("type", "text"); | ||
} else { | ||
$(this) | ||
.attr("class", "fa fa-eye fa-lg") | ||
.prev("input") | ||
.attr("type", "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.
파일명 보면 알 수 있는 부분이라 없어도 되는 주석인 것 같아요!!