-
Notifications
You must be signed in to change notification settings - Fork 46
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
[김도훈] Sprint 11 #331
Merged
baeggmin
merged 15 commits into
codeit-bootcamp-frontend:Next-김도훈
from
kimdohoon2:Next-김도훈-sprint11
Dec 10, 2024
The head ref may contain hidden characters: "Next-\uAE40\uB3C4\uD6C8-sprint11"
Merged
[김도훈] Sprint 11 #331
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
edf39eb
feat : 홈, 로그인, 회원가입 페이지 이미지 아이콘 추가
kimdohoon2 0e8ebd6
feat : 회원가입 페이지, 아이템페이지 추가 Header.tsx 경로변경
kimdohoon2 3b0de3e
feat : 메인페이지 ui완성
kimdohoon2 5840974
feat : 로그인페이지 ui완성
kimdohoon2 b8177c2
feat : 필요한 페이지에서만 Header렌더링 구현완료
kimdohoon2 f237918
feat : 회원가입, 로그인 공통컴퍼넌트 구현완료
kimdohoon2 0bde56e
feat : 로그인 페이지, 회원가입 페이지 구현완료, type 변경
kimdohoon2 d5a9e33
feat : layout부분 오류메세지 떠서 object-contain으로 변경
kimdohoon2 64e9c76
feat : fill 대신 object-contain으로 css변경
kimdohoon2 07831d6
feat : 로그인 버튼 구현완료
kimdohoon2 1b6859f
feat : 로그인, 회원가입 api 구현완료
kimdohoon2 ba2c6d9
feat : 회원가입, 로그인 기능구현완료
kimdohoon2 da9a6b7
feat : accessToken 받아서 저장완료
kimdohoon2 ef174a3
feat : token값을 활용해 로그인 버튼 마이페이지 이미지로 변경, 로그아웃 기능구현완료
kimdohoon2 89eb3be
feat : 경로 변경
kimdohoon2 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
const url = "https://panda-market-api.vercel.app/auth"; | ||
export const signup = async (data: { | ||
email: string; | ||
nickname: string; | ||
password: string; | ||
passwordConfirmation?: string; | ||
}) => { | ||
const response = await fetch(`${url}/signUp`, { | ||
method: "POST", | ||
headers: { "Content-Type": "application/json" }, | ||
body: JSON.stringify(data), | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error("회원가입 실패"); | ||
} | ||
|
||
return response.json(); | ||
}; | ||
|
||
export const login = async (data: { email: string; password: string }) => { | ||
const response = await fetch(`${url}/signIn`, { | ||
method: "POST", | ||
headers: { "Content-Type": "application/json" }, | ||
body: JSON.stringify(data), | ||
}); | ||
|
||
if (!response.ok) { | ||
throw new Error("로그인 실패"); | ||
} | ||
|
||
const responseData = await response.json(); | ||
return responseData; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,319 @@ | ||
"use client"; | ||
|
||
import Image from "next/image"; | ||
import Link from "next/link"; | ||
import { AuthFormProps } from "../type/type"; | ||
import { useState, useEffect } from "react"; | ||
import { useRouter } from "next/navigation"; | ||
import { signup, login } from "../api/api"; | ||
|
||
export default function CommonForm({ type }: AuthFormProps) { | ||
const isLogin = type === "login"; | ||
const [email, setEmail] = useState(""); | ||
const [emailError, setEmailError] = useState(""); | ||
const [password, setPassword] = useState(""); | ||
const [passwordError, setPasswordError] = useState(""); | ||
const [repassword, setRePassword] = useState(""); | ||
const [repasswordError, setRePasswordError] = useState(""); | ||
const [nickname, setNickname] = useState(""); | ||
const [nicknameError, setNicknameError] = useState(""); | ||
const [showPassword, setShowPassword] = useState(false); | ||
const [showRePassword, setShowRePassword] = useState(false); | ||
const router = useRouter(); | ||
|
||
useEffect(() => {}, [email, nickname, password, repassword]); | ||
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. 이 줄은 왜 필요한건가요?? 실수로 넣으신거겠죵? |
||
|
||
const handleSubmit = async (event: React.FormEvent) => { | ||
event.preventDefault(); | ||
|
||
try { | ||
if (type === "signup") { | ||
const signupData = { | ||
email, | ||
nickname, | ||
password, | ||
passwordConfirmation: repassword, | ||
}; | ||
await signup(signupData); // 회원가입 API 호출 | ||
router.push("/login"); | ||
alert("회원가입 성공!"); | ||
} else if (type === "login") { | ||
const loginData = { | ||
email, | ||
password, | ||
}; | ||
const data = await login(loginData); // 로그인 API 호출 | ||
if (data && data.accessToken) { | ||
localStorage.setItem("token", data.accessToken); // 토큰 저장 | ||
router.push("/"); | ||
alert("로그인 성공!"); | ||
} else { | ||
alert("로그인 실패: 토큰이 없습니다."); | ||
} | ||
} | ||
} catch (error: any) { | ||
const errorMessage = | ||
error.response?.data?.message || | ||
error.message || | ||
"알 수 없는 오류가 발생했습니다."; | ||
alert(errorMessage); | ||
} | ||
}; | ||
const validateEmail = (email: string) => { | ||
if (!email) { | ||
setEmailError("이메일을 입력해주세요"); | ||
} else if (!/\S+@\S+\.\S+/.test(email)) { | ||
setEmailError("올바른 이메일 형식을 입력해주세요"); | ||
} else { | ||
setEmailError(""); | ||
} | ||
}; | ||
|
||
const validatePassword = (password: string) => { | ||
if (!password) { | ||
setPasswordError("비밀번호를 입력해주세요"); | ||
} else if (password.length < 8) { | ||
setPasswordError("비밀번호는 8자리 이상이어야 합니다"); | ||
} else { | ||
setPasswordError(""); | ||
} | ||
}; | ||
const validateRePassword = (repassword: string, password: string) => { | ||
if (!repassword) { | ||
setRePasswordError("비밀번호를 다시 한 번 입력해주세요"); | ||
} else if (repassword !== password) { | ||
setRePasswordError("비밀번호가 일치하지 않습니다."); | ||
} else { | ||
setRePasswordError(""); | ||
} | ||
}; | ||
|
||
// 이메일 입력 변경 시 에러 메시지 제거 | ||
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setEmail(e.target.value); | ||
setEmailError(""); // 실시간으로 에러 메시지 제거 | ||
validateEmail(e.target.value); // 실시간 이메일 유효성 검사 | ||
}; | ||
|
||
// 비밀번호 입력 변경 시 에러 메시지 제거 | ||
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setPassword(e.target.value); | ||
setPasswordError(""); // 실시간으로 에러 메시지 제거 | ||
validatePassword(e.target.value); // 실시간 비밀번호 유효성 검사 | ||
}; | ||
|
||
// 비밀번호 입력 변경 시 에러 메시지 제거 | ||
const handleRePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setRePassword(e.target.value); | ||
setRePasswordError(""); // 실시간으로 에러 메시지 제거 | ||
validateRePassword(e.target.value, password); // 실시간 비밀번호 확인 유효성 검사 | ||
}; | ||
|
||
// 닉네임 입력 변경 시 | ||
const handleNicknameChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setNickname(e.target.value); | ||
setNicknameError(""); | ||
}; | ||
|
||
const handlePasswordVisibilityToggle = () => { | ||
setShowPassword((prevState) => !prevState); // 비밀번호 보이기/숨기기 상태 토글 | ||
}; | ||
|
||
const handleRePasswordVisibilityToggle = () => { | ||
setShowRePassword((prevState) => !prevState); // 비밀번호 확인 보이기/숨기기 상태 토글 | ||
}; | ||
return ( | ||
<> | ||
<div className={`mt-[80px] ${isLogin ? "md:mt-[140px]" : ""}`}> | ||
<div className="flex flex-col items-center pl-[16px] pr-[16px]"> | ||
<div className="w-full"> | ||
<Link | ||
className="w-full flex items-center justify-center gap-[11px]" | ||
href={"/"} | ||
> | ||
<div className="w-full h-auto max-w-[51px] md:max-w-[103px]"> | ||
<Image | ||
className="object-contain" | ||
src="/head/logo_face.png" | ||
alt="로고" | ||
width={104} | ||
height={105} | ||
/> | ||
</div> | ||
<div className="w-full h-auto max-w-[133px] md:max-w-[259px]"> | ||
<Image | ||
className="object-contain" | ||
src="/head/logo_txt.png" | ||
alt="로고" | ||
width={259} | ||
height={64} | ||
/> | ||
</div> | ||
</Link> | ||
</div> | ||
<form onSubmit={handleSubmit} className="mt-[60px]"> | ||
<div className="w-[343px] md:w-[640px] flex flex-col gap-[16px] md:gap-[24px]"> | ||
<div className="flex flex-col"> | ||
<label className="mb-[8px]" htmlFor="pandaEmail"> | ||
이메일 | ||
</label> | ||
<input | ||
className="w-full h-[56px] bg-gray100 pl-[24px] rounded-[12px]" | ||
type="email" | ||
placeholder="이메일을 입력해주세요" | ||
id="pandaEmail" | ||
value={email} | ||
onChange={handleEmailChange} | ||
required | ||
/> | ||
<div className="text-red">{emailError}</div> | ||
</div> | ||
{!isLogin && ( | ||
<div className="flex flex-col"> | ||
<label className="mb-[8px]" htmlFor="pandaName"> | ||
닉네임 | ||
</label> | ||
<input | ||
className="w-full h-[56px] bg-gray100 pl-[24px] rounded-[12px]" | ||
type="text" | ||
placeholder="닉네임" | ||
id="pandaName" | ||
value={nickname} | ||
onChange={handleNicknameChange} | ||
required | ||
/> | ||
</div> | ||
)} | ||
<div className="flex flex-col"> | ||
<label className="mb-[8px]" htmlFor="pandaPassword"> | ||
비밀번호 | ||
</label> | ||
<div className="relative"> | ||
<input | ||
className="w-full h-[56px] bg-gray100 pl-[24px] rounded-[12px]" | ||
type={showPassword ? "text" : "password"} | ||
placeholder="비밀번호를 입력해주세요" | ||
id="pandaPassword" | ||
value={password} | ||
onChange={handlePasswordChange} | ||
required | ||
/> | ||
<div className="absolute top-1/2 right-6 w-[22px] h-[19px] transform -translate-y-1/2 "> | ||
<div | ||
className="w-full h-auto max-w-[22px] absolute opacity-1 pointer-events-auto" | ||
onClick={handlePasswordVisibilityToggle} | ||
> | ||
<Image | ||
className="object-contain" | ||
src={ | ||
showPassword | ||
? "/icon/visible.png" | ||
: "/icon/visibility.png" | ||
} | ||
alt="클릭시비밀번호보기" | ||
width={22} | ||
height={19} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="text-red">{passwordError}</div> | ||
</div> | ||
{!isLogin && ( | ||
<div className="flex flex-col"> | ||
<label className="mb-[8px]" htmlFor="repandaPassword"> | ||
비밀번호 확인 | ||
</label> | ||
<div className="relative"> | ||
<input | ||
className="w-full h-[56px] bg-gray100 pl-[24px] rounded-[12px]" | ||
type={showRePassword ? "text" : "password"} | ||
placeholder="비밀번호를 다시 한 번 입력해주세요" | ||
id="repandaPassword" | ||
value={repassword} | ||
onChange={handleRePasswordChange} | ||
required | ||
/> | ||
<div | ||
className="absolute top-1/2 right-6 w-[22px] h-[19px] transform -translate-y-1/2" | ||
onClick={handleRePasswordVisibilityToggle} | ||
> | ||
<Image | ||
className="object-contain" | ||
src={ | ||
showRePassword | ||
? "/icon/visible.png" | ||
: "/icon/visibility.png" | ||
} | ||
alt="클릭시비밀번호보기" | ||
width={22} | ||
height={19} | ||
/> | ||
</div> | ||
</div> | ||
<div className="text-red">{repasswordError}</div> | ||
</div> | ||
)} | ||
<button | ||
type="submit" | ||
className="w-full h-[56px] text-center text-background text-[20px] text-gray100 bg-gray400 rounded-[40px]" | ||
disabled={ | ||
!!emailError || | ||
!!passwordError || | ||
(!isLogin && (!!nicknameError || !!repasswordError)) | ||
} | ||
> | ||
{isLogin ? "로그인" : "회원가입"} | ||
</button> | ||
|
||
<div className="flex justify-between items-center w-full h-[74px] bg-mainbg rounded-[8px] pl-[24px] pr-[24px]"> | ||
<p className="w-full">간편 로그인하기</p> | ||
<div className="w-full flex justify-end gap-[16px]"> | ||
<div className="w-[42px] h-[42px] rounded-full relative bg-background"> | ||
<Link | ||
className="w-[22px] h-auto absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" | ||
href="https://www.google.com/" | ||
> | ||
<Image | ||
className="object-contain" | ||
src="/icon/google.png" | ||
alt="구글로고" | ||
width={22} | ||
height={22} | ||
/> | ||
</Link> | ||
</div> | ||
<div className="w-[42px] h-[42px] rounded-full relative bg-background"> | ||
<Link | ||
className="w-[22px] h-auto absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" | ||
href="https://www.kakaocorp.com/page/" | ||
> | ||
<Image | ||
className="object-contain" | ||
src="/icon/kakao.png" | ||
alt="카카오톡로고" | ||
width={26} | ||
height={24} | ||
/> | ||
</Link> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="flex justify-center gap-[4px]"> | ||
<span className="text-[14px]"> | ||
{isLogin ? "판다마켓이 처음이신가요?" : "이미 회원이신가요?"} | ||
</span> | ||
<Link | ||
className="text-skyblue" | ||
href={isLogin ? "/signup" : "/login"} | ||
> | ||
{isLogin ? "회원가입" : "로그인"} | ||
</Link> | ||
</div> | ||
</div> | ||
</form> | ||
</div> | ||
</div> | ||
</> | ||
); | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
중복되는 부분을 공통 컴포넌트화 하시려 한 것 같은데요, 이 영역은 오히려 분리해서 작성하는게 유지보수 측면에서 더 낫지 않을까 싶네요..🤔
하나의 컴포넌트에 로그인과 회원가입 모두 다루려고 하다보니 state 가 많아져 가독성이 떨어지고, handleSubmit 라는 하나의 함수 안에서 분기처리를 하게되면 나중에 더 복잡한 로직이 추가되었을 때 점점 더 읽기 어려운 코드가 될 것 같아요.
차라리 공통된 로직 부분을 custom hook 으로 분리해보시는건 어떨까요? email, password, showPassword 등의 로직은 훅으로 빼서 재사용하게 만들면 좋을 것 같습니다.