Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ import Footer from "@/src/layouts/Footer";
import "@/styles/Home.css";
import "@/styles/Footer.css";
import "@/styles/globals.css";
import "@/styles/Login.css";
import type { AppProps } from "next/app";
import { useRouter } from "next/router";

export default function App({ Component, pageProps }: AppProps) {
const { pathname } = useRouter();
const login = pathname !== "/login";
const signup = pathname !== "/signup";

return (
<>
<Header />
{login && signup && <Header />}
<Component {...pageProps} />
<Footer />
{login && signup && <Footer />}
Comment on lines +17 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

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

저번에 말씀드렸던 getLayout 기능을 활용해서 페이지 별로 Layout을 지정해보시는 것도 좋을 것 같습니다.

</>
);
}
182 changes: 180 additions & 2 deletions pages/login/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,183 @@
import StyledInner from "@/src/layouts/StyledInner.style";
import Image from "next/image";
import Link from "next/link";
import Logo from "@/src/assets/login/logo.svg";
import GoogleLogin from "@/src/assets/login/easyLogin_google.svg";
import KakaoLogin from "@/src/assets/login/easyLogin_kakao.svg";
import EyeIcon from "@/src/assets/login/eye_icon.svg";
import EyeShowIcon from "@/src/assets/login/eye_show_icon.svg";
import { ChangeEvent, FocusEvent, FormEvent, useState } from "react";
import { useRouter } from "next/router";

type ValuesProps = {
email: string;
password: string;
};

export default function Login() {
return <StyledInner>Login</StyledInner>;
const [loginInfo, setLoginInfo] = useState<ValuesProps>({
email: "",
password: "",
});
const [isPasswordHidden, setIsPasswordHidden] = useState(false);
const [emailErrorMessage, setEmailErrorMessage] = useState("");
Copy link
Collaborator

Choose a reason for hiding this comment

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

에러가 없는 경우에 빈 문자열 보다는 null 같은 값으로 명시적으로 표시해주시는 것도 좋을 것 같습니다.

const [passwordErrorMessage, setPasswordErrorMessage] = useState("");
const router = useRouter();

const pattern = /^[A-Za-z0-9]+@[A-Za-z0-9]+\.[A-Za-z0-9]+/;

const loginValid =
pattern.test(loginInfo.email.trim()) && loginInfo.password.length >= 8;

const handleInputBlur = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.value.trim()) {
setEmailErrorMessage("이메일을 입력해 주세요.");
} else if (!pattern.test(e.target.value.trim())) {
setEmailErrorMessage("잘못된 이메일 형식입니다.");
} else {
setEmailErrorMessage("");
}
};

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;

setLoginInfo((prevLoginInfo) => ({
...prevLoginInfo,
[name]: value,
}));

if (name === "password") {
if (loginInfo.password.length < 7) {
setPasswordErrorMessage("비밀번호를 8자 이상 입력해 주세요.");
} else {
setPasswordErrorMessage("");
}
}
Comment on lines +44 to +55
Copy link
Collaborator

Choose a reason for hiding this comment

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

password 상태가 반영된 후에 에러가 뜰 것 같아서 에러를 먼저 처리해주시고 상태가 반영되게 하는건 어떨까요?

};

const handlePasswordBlur = (e: FocusEvent<HTMLInputElement>) => {
if (e.target.value.trim() === "") {
setPasswordErrorMessage("비밀번호를 입력해 주세요.");
}
};

const handleClick = () => {
setIsPasswordHidden(!isPasswordHidden);
};

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

const res = await fetch("https://panda-market-api.vercel.app/auth/signIn", {
method: "POST",
headers: {
"Content-type": "application/json",
},
body: JSON.stringify(loginInfo),
});

const data = await res.json();

if (res.ok) {
localStorage.setItem("accessToken", data.accessToken);
router.push("/");
}
};

return (
<div className="wrapper login">
<div className="inner">
<h1>
<Link className="logo" href={"/"}>
<Image src={Logo} alt="logo" />
Copy link
Collaborator

Choose a reason for hiding this comment

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

next/image는 width / height나 fill이 없으면 잘 동작하지 않을텐데 괜찮나요?

</Link>
</h1>

<div className="loginWrap">
<form onSubmit={handleSubmit}>
<div className="input">
<label htmlFor="email-input">이메일</label>
<input
name="email"
id="email-input"
type="text"
placeholder="이메일을 입력해주세요."
autoComplete="username"
className={emailErrorMessage ? "error" : ""}
value={loginInfo.email}
onChange={handleChange}
onBlur={handleInputBlur}
/>

<span className="errorMessage">{emailErrorMessage}</span>
</div>

<div className="input">
<label htmlFor="password-input">비밀번호</label>

<div className="password">
<input
name="password"
id="password-input"
type={isPasswordHidden ? "text" : "password"}
placeholder="비밀번호를 입력해주세요"
autoComplete="current-password"
value={loginInfo.password}
onChange={handleChange}
onBlur={handlePasswordBlur}
/>
<button
className="passwordToggleBtn"
type="button"
onClick={handleClick}
>
{isPasswordHidden ? (
<Image src={EyeShowIcon} alt="" />
) : (
<Image src={EyeIcon} alt="" />
)}
</button>
</div>

<span className="errorMessage">{passwordErrorMessage}</span>
</div>

<button className="loginBtn" type="submit" disabled={!loginValid}>
로그인
</button>
</form>
</div>

<div className="easyLogin">
<p>간편 로그인하기</p>

<ul>
<li>
<Link
className="google"
href="https://google.com"
target="_blank"
>
<Image src={GoogleLogin} alt="구글 로그인" />
</Link>
</li>

<li>
<Link
className="kakao"
href="https://www.kakaocorp.com/page/"
target="_blank"
>
<Image src={KakaoLogin} alt="카카오 로그인" />
</Link>
</li>
</ul>
</div>

<p className="signup">
판다마켓이 처음이신가요? <Link href={"/signup"}>회원가입</Link>
</p>
</div>
</div>
);
}
Loading
Loading