diff --git a/components/Input.tsx b/components/Input.tsx index fbecede81..672365dd9 100644 --- a/components/Input.tsx +++ b/components/Input.tsx @@ -1,31 +1,66 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import Image from "next/image"; +import { postCheckEmail } from "@/pages/api/api"; import styles from "@/styles/Input.module.css"; import eyeOff from "@/public/images/eye-off.svg"; import eyeOn from "@/public/images/eye-on.svg"; interface InputProp { + page?: string; + passwordValue?: string; inputType: string; + onChange: (value: string) => void; + onSetErrMsg: (value: string) => void; + isError: string; } -export default function Input({ inputType }: InputProp) { +export default function Input({ + page, + passwordValue, + inputType, + onChange, + onSetErrMsg, + isError, +}: InputProp) { const [isFocused, setIsFocused] = useState(false); const [isShowPassword, setIsShowPassword] = useState(false); - const [type, setType] = useState(inputType); + const [type, setType] = useState( + inputType === "passwordChk" ? "password" : inputType + ); const [errorMsg, setErrorMsg] = useState(""); const emailChk = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; const passwordChk = /^(?=.*\d)(?=.*[a-z])[A-Za-z\d@$!%*?&]{8,}$/; - const handleError = (e: React.FocusEvent) => { + //에러메시지가 바뀔때마다 에러메시지 초기화 + useEffect(() => { + onSetErrMsg(errorMsg); + }, [errorMsg]); + + const handleError = async (e: React.FocusEvent) => { const value = e.target.value; setIsFocused(false); - if (!value) { - setErrorMsg("값을 입력해주세요"); + + if (!value && inputType === "id") { + setErrorMsg("이메일을 입력해 주세요."); + } else if (!value && inputType === "password") { + setErrorMsg("비밀번호를 입력해 주세요."); + } else if (!value && inputType === "passwordChk") { + setErrorMsg("비밀번호를 다시 한번 입력해 주세요."); } else if (inputType === "id" && !emailChk.test(value)) { setErrorMsg("올바른 이메일 주소를 입력해주세요."); } else if (inputType === "password" && !passwordChk.test(value)) { setErrorMsg("올바른 비밀번호를 입력해주세요."); + } else if (inputType === "passwordChk" && passwordValue !== value) { + setErrorMsg("비밀번호가 일치하지 않아요."); + } else if (page === "signUp" && inputType === "id" && value) { + //회원가입 페이지에서의 이메일 input태그이고 값이 있는경우 + const response = await postCheckEmail(value); + if (response.status !== 200) { + setErrorMsg("이미 사용 중인 이메일입니다."); + } else { + setErrorMsg(""); + } } else { setErrorMsg(""); } @@ -40,20 +75,32 @@ export default function Input({ inputType }: InputProp) { } }; + const handleChange = (e: React.ChangeEvent) => { + const newValue = e.target.value; + onChange(newValue); // 부모 컴포넌트로 값을 전달 + }; + return ( <>
setIsFocused(true)} onBlur={handleError} + onChange={handleChange} /> - {inputType === "password" && ( + {(inputType === "password" || inputType === "passwordChk") && ( {isShowPassword )}
- {errorMsg &&

{errorMsg}

} ); } diff --git a/components/SignLogoFrame.tsx b/components/SignLogoFrame.tsx new file mode 100644 index 000000000..de83bd145 --- /dev/null +++ b/components/SignLogoFrame.tsx @@ -0,0 +1,26 @@ +import Image from "next/image"; +import styles from "@/styles/SignLogoFrame.module.css"; +import logo from "@/public/images/logo.svg"; + +interface SignLogoProp { + type: string; +} + +export default function SignLogoFrame({ type }: SignLogoProp) { + return ( +
+ 로고이미지 +
+ + {type === "signIn" ? "회원이 아니신가요?" : "이미 회원이신가요?"} + + + {type === "signIn" ? "회원 가입하기" : "로그인 하기"} + +
+
+ ); +} diff --git a/pages/api/api.tsx b/pages/api/api.tsx index 9c2116760..7679f9237 100644 --- a/pages/api/api.tsx +++ b/pages/api/api.tsx @@ -44,3 +44,41 @@ export async function getFolderLink(id: number) { const folderLink = await response.json(); return folderLink; } + +export async function postSignIn(id: string, password: string) { + const response = await fetch(`${BASE_URL}/api/sign-in`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: id, + password: password, + }), + }); + + return response; +} + +export async function postCheckEmail(id: string) { + const response = await fetch(`${BASE_URL}/api/check-email`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: id, + }), + }); + + return response; +} + +export async function postSignUp(id: string, password: string) { + const response = await fetch(`${BASE_URL}/api/sign-up`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: id, + password: password, + }), + }); + + return response; +} diff --git a/pages/index.tsx b/pages/index.tsx index 298551a63..598a78ae0 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,6 +1,5 @@ import { Inter } from "next/font/google"; import Link from "next/link"; -import Input from "@/components/Input"; const inter = Inter({ subsets: ["latin"] }); @@ -13,11 +12,12 @@ export default function Home() {

folder페이지로 이동

-
- -
-
- + +

signin페이지로 이동

+ + +

signup페이지로 이동

+ ); } diff --git a/pages/signin.tsx b/pages/signin.tsx new file mode 100644 index 000000000..e7bcb134a --- /dev/null +++ b/pages/signin.tsx @@ -0,0 +1,110 @@ +import React, { FormEvent, useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import Image from "next/image"; +import Link from "next/link"; +import Input from "@/components/Input"; +import SignLogoFrame from "@/components/SignLogoFrame"; +import { postSignIn } from "./api/api"; +import styles from "@/styles/SignInPage.module.css"; +import kakaoIcon from "@/public/images/kakao.svg"; +import googleIcon from "@/public/images/google.svg"; + +export default function SignIn() { + const [emailValue, setEmailValue] = useState(""); + const [passwordValue, setPasswordValue] = useState(""); + const [emailError, setEmailError] = useState(""); + const [passwordError, setPasswordError] = useState(""); + + const router = useRouter(); + + useEffect(() => { + const accessToken = localStorage.getItem("accessToken"); + if (accessToken) { + router.push("/folder"); + } + }, []); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + + const response = await postSignIn(emailValue, passwordValue); + + const { data } = await response.json(); + + if (response.status === 200) { + router.push("/folder"); + localStorage.setItem("accessToken", data.accessToken); + } else { + setEmailError("이메일을 확인해 주세요."); + setPasswordError("비밀번호를 확인해 주세요."); + } + }; + + const handleEmailChange = (value: string) => { + setEmailValue(value); + }; + + const handlePasswordChange = (value: string) => { + setPasswordValue(value); + }; + + const handleSetIdErrMsg = (value: string) => { + setEmailError(value); + }; + + const handleSetPasswordErrMsg = (value: string) => { + setPasswordError(value); + }; + + return ( +
+
+ +
+
+ 이메일 + + {emailError &&

{emailError}

} +
+ +
+ 비밀번호 + + {passwordError &&

{passwordError}

} +
+
+ + + +
+ 소셜 로그인 +
+
+ + 구글 아이콘 + +
+ +
+ + 카카오톡 아이콘 + +
+
+
+ +
+ ); +} diff --git a/pages/signup.tsx b/pages/signup.tsx new file mode 100644 index 000000000..923c68793 --- /dev/null +++ b/pages/signup.tsx @@ -0,0 +1,135 @@ +import React, { FormEvent, useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import Image from "next/image"; +import Link from "next/link"; +import Input from "@/components/Input"; +import SignLogoFrame from "@/components/SignLogoFrame"; +import { postSignUp } from "./api/api"; +import styles from "@/styles/SignUpPage.module.css"; +import kakaoIcon from "@/public/images/kakao.svg"; +import googleIcon from "@/public/images/google.svg"; + +export default function SignUp() { + const [emailValue, setEmailValue] = useState(""); + const [passwordValue, setPasswordValue] = useState(""); + const [passwordChkValue, setPasswordChkValue] = useState(""); + const [emailError, setEmailError] = useState(""); + const [passwordError, setPasswordError] = useState(""); + const [passwordChkError, setPasswordChkError] = useState(""); + + const router = useRouter(); + + useEffect(() => { + const accessToken = localStorage.getItem("accessToken"); + if (accessToken) { + router.push("/folder"); + } + }, []); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + if (emailError || passwordError || passwordChkError) { + //에러가 하나라도 있을 경우에는 회원가입 실행 X + } else { + //회원가입 로직 + const response = await postSignUp(emailValue, passwordValue); + const { data } = await response.json(); + if (response.status === 200) { + router.push("/folder"); + localStorage.setItem("accessToken", data.accessToken); + } else { + } + } + }; + + const handleEmailChange = (value: string) => { + setEmailValue(value); + }; + + const handlePasswordChange = (value: string) => { + setPasswordValue(value); + }; + + const handlePasswordChkChange = (value: string) => { + setPasswordChkValue(value); + }; + + const handleSetIdErrMsg = (value: string) => { + setEmailError(value); + }; + + const handleSetPasswordErrMsg = (value: string) => { + setPasswordError(value); + }; + + const handleSetPasswordChkErrMsg = (value: string) => { + setPasswordChkError(value); + }; + + return ( +
+
+ +
+
+ 이메일 + + {emailError &&

{emailError}

} +
+ +
+ 비밀번호 + + {passwordError &&

{passwordError}

} +
+ +
+ 비밀번호 확인 + + {passwordChkError && ( +

{passwordChkError}

+ )} +
+
+ + + +
+ 다른 방식으로 가입하기 +
+
+ + 구글 아이콘 + +
+ +
+ + 카카오톡 아이콘 + +
+
+
+ +
+ ); +} diff --git a/public/images/google.svg b/public/images/google.svg new file mode 100644 index 000000000..2d2685fe8 --- /dev/null +++ b/public/images/google.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/styles/SignInPage.module.css b/styles/SignInPage.module.css new file mode 100644 index 000000000..c942ec532 --- /dev/null +++ b/styles/SignInPage.module.css @@ -0,0 +1,121 @@ +.container { + display: flex; + width: 100%; + height: 982px; + padding: 238px 0px 253px 0px; + justify-content: center; + align-items: center; + background: var(--Linkbrary-bg, #f0f6ff); +} + +.logInFrame { + display: flex; + width: 400px; + flex-direction: column; + align-items: center; + gap: 32px; +} + +.inputCommonFrame { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 24px; +} + +.inputFrame { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 12px; +} + +.inputFrame span { + color: var(--black, #000); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; +} + +#logInButton { + display: flex; + width: 100%; + padding: 16px 20px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 8px; + background: var( + --gra-purpleblue-to-skyblue, + linear-gradient(91deg, #6d6afe 0.12%, #6ae3fe 101.84%) + ); + + border: none; + color: var(--Grey-Light, #f5f5f5); + font-family: Pretendard; + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: normal; + cursor: pointer; +} + +.socialLoginFrame { + display: flex; + width: 100%; + padding: 12px 24px; + justify-content: space-between; + align-items: center; + border-radius: 8px; + border: 1px solid var(--Linkbrary-gray20, #ccd5e3); + background: var(--Linkbrary-gray10, #e7effb); +} + +.socialLoginFrame span { + color: var(--Linkbrary-gray100, #373740); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; +} + +.socialLogin { + display: flex; + align-items: flex-start; + gap: 16px; +} + +.iconGoogleBox { + width: 22px; + height: 22px; + display: flex; + padding: 12px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 37.333px; + background: white; +} + +.iconKakaoBox { + width: 22px; + height: 22px; + display: flex; + padding: 12px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 37.333px; + background: #fee500; +} + +.errMsg { + color: var(--Linkbrary-red, #ff5b56); + /* Linkbrary/body2-regular */ + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; +} diff --git a/styles/SignLogoFrame.module.css b/styles/SignLogoFrame.module.css new file mode 100644 index 000000000..59c7f5a4d --- /dev/null +++ b/styles/SignLogoFrame.module.css @@ -0,0 +1,33 @@ +.logoFrame { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; +} + +#logoImg { + width: 210.583px; + height: 38px; +} + +.commentFrame { + display: flex; + align-items: flex-start; + gap: 8px; +} + +#comment { + color: var(--black, #000); + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; /* 150% */ +} + +#linkComment { + color: var(--Linkbrary-primary-color, #6d6afe); + font-size: 16px; + font-style: normal; + font-weight: 600; + line-height: normal; +} diff --git a/styles/SignUpPage.module.css b/styles/SignUpPage.module.css new file mode 100644 index 000000000..a7facbccc --- /dev/null +++ b/styles/SignUpPage.module.css @@ -0,0 +1,121 @@ +.container { + display: flex; + width: 100%; + height: 982px; + padding: 238px 0px 253px 0px; + justify-content: center; + align-items: center; + background: var(--Linkbrary-bg, #f0f6ff); +} + +.logInFrame { + display: flex; + width: 400px; + flex-direction: column; + align-items: center; + gap: 32px; +} + +.inputCommonFrame { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 24px; +} + +.inputFrame { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 12px; +} + +.inputFrame span { + color: var(--black, #000); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; +} + +#signUpButton { + display: flex; + width: 100%; + padding: 16px 20px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 8px; + background: var( + --gra-purpleblue-to-skyblue, + linear-gradient(91deg, #6d6afe 0.12%, #6ae3fe 101.84%) + ); + + border: none; + color: var(--Grey-Light, #f5f5f5); + font-family: Pretendard; + font-size: 18px; + font-style: normal; + font-weight: 600; + line-height: normal; + cursor: pointer; +} + +.socialLoginFrame { + display: flex; + width: 100%; + padding: 12px 24px; + justify-content: space-between; + align-items: center; + border-radius: 8px; + border: 1px solid var(--Linkbrary-gray20, #ccd5e3); + background: var(--Linkbrary-gray10, #e7effb); +} + +.socialLoginFrame span { + color: var(--Linkbrary-gray100, #373740); + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; +} + +.socialLogin { + display: flex; + align-items: flex-start; + gap: 16px; +} + +.iconGoogleBox { + width: 22px; + height: 22px; + display: flex; + padding: 12px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 37.333px; + background: white; +} + +.iconKakaoBox { + width: 22px; + height: 22px; + display: flex; + padding: 12px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 37.333px; + background: #fee500; +} + +.errMsg { + color: var(--Linkbrary-red, #ff5b56); + /* Linkbrary/body2-regular */ + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; +}