diff --git a/public/Icons/google.svg b/public/Icons/google.svg
new file mode 100644
index 0000000000..7ae503d30e
--- /dev/null
+++ b/public/Icons/google.svg
@@ -0,0 +1,10 @@
+
diff --git a/public/Icons/kakaolg.svg b/public/Icons/kakaolg.svg
new file mode 100644
index 0000000000..b5d0a6169a
--- /dev/null
+++ b/public/Icons/kakaolg.svg
@@ -0,0 +1,12 @@
+
diff --git a/src/components/signinPage/ErrorMesage.tsx b/src/components/signinPage/ErrorMesage.tsx
new file mode 100644
index 0000000000..a7f97075ab
--- /dev/null
+++ b/src/components/signinPage/ErrorMesage.tsx
@@ -0,0 +1,14 @@
+import React from "react";
+import styled from "styled-components";
+
+const Error = styled.p`
+ color: #ff5b56;
+ font-size: 14px;
+ font-weight: 400;
+`;
+
+function ErrorMesage({ text }: { text: string | undefined }) {
+ return {text};
+}
+
+export default ErrorMesage;
diff --git a/src/components/signinPage/Input.tsx b/src/components/signinPage/Input.tsx
index 5970ec99d4..f2961800b5 100644
--- a/src/components/signinPage/Input.tsx
+++ b/src/components/signinPage/Input.tsx
@@ -3,18 +3,20 @@ import React, { InputHTMLAttributes, RefObject, useRef, useState } from "react";
import styled from "styled-components";
import setPwOff from "@/public/Icons/eye-off.svg";
import setPwOn from "@/public/Icons/eye-on.svg";
-import { useForm } from "react-hook-form";
+import { FieldErrors, UseFormRegisterReturn, useForm } from "react-hook-form";
+import ErrorMesage from "./ErrorMesage";
type InputStyledProps = {
$error?: boolean;
};
const Layout = styled.div`
- width: 400px;
+ width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
position: relative;
+ margin-bottom: 6px;
`;
const Label = styled.label`
@@ -45,38 +47,27 @@ const ImgPosition = styled.div`
right: 18px;
`;
-const ErrorMesage = styled.p`
- color: #ff5b56;
- font-size: 14px;
- font-weight: 400;
-`;
-
interface InputProps extends InputHTMLAttributes {
id: string;
placeholder: string;
- error?: boolean;
- label: string;
+ errors: FieldErrors;
+ label?: string;
+ validation?: UseFormRegisterReturn;
}
-function Input({ id, type = "text", placeholder, error, label }: InputProps) {
+function Input({
+ id,
+ type = "text",
+ placeholder,
+ errors,
+ label,
+ validation,
+}: InputProps) {
const [pwState, setPwState] = useState(false);
- const passwordRef = useRef(null);
-
- const {
- register,
- formState: { errors },
- } = useForm();
+ const error = errors[id];
const toggleEyesButton = () => {
- if (passwordRef.current) {
- if (pwState) {
- passwordRef.current.type = "text";
- setPwState(false);
- } else {
- passwordRef.current.type = "password";
- setPwState(true);
- }
- }
+ setPwState((prev) => !prev);
};
return (
@@ -84,25 +75,23 @@ function Input({ id, type = "text", placeholder, error, label }: InputProps) {
{type === "password" && (
{pwState ? (
-
- ) : (
+ ) : (
+
)}
)}
- {errors.category?.type === "required" && (
- 필수 입력사항입니다.
- )}
+ {errors && }
>
);
}
diff --git a/src/components/signinPage/LogoBox.tsx b/src/components/signinPage/LogoBox.tsx
index 859e00d61e..c1b46027ce 100644
--- a/src/components/signinPage/LogoBox.tsx
+++ b/src/components/signinPage/LogoBox.tsx
@@ -17,7 +17,6 @@ const Text = styled.p`
font-size: 16px;
font-weight: 400;
line-height: 24px; /* 150% */
- margin-bottom: 30px;
`;
const LinkStyle = {
@@ -26,16 +25,21 @@ const LinkStyle = {
fontWeight: 600,
};
-function LogoBox() {
+interface LogoBox {
+ text: string;
+ linkText: string;
+}
+
+function LogoBox({ text, linkText }: LogoBox) {
return (
- 회원이 아니신가요?{" "}
+ {text}{" "}
- 회원 가입하기
+ {linkText}
diff --git a/src/components/signinPage/SocialBox.tsx b/src/components/signinPage/SocialBox.tsx
new file mode 100644
index 0000000000..86bdebc3bb
--- /dev/null
+++ b/src/components/signinPage/SocialBox.tsx
@@ -0,0 +1,47 @@
+import Image from "next/image";
+import React from "react";
+import googleIcon from "@/public/Icons/google.svg";
+import kakao from "@/public/Icons/kakaolg.svg";
+import styled from "styled-components";
+import Link from "next/link";
+
+const Layout = styled.div`
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 24px;
+ margin: 0 auto;
+ border-radius: 8px;
+ border: 1px solid var(--Linkbrary-gray20, #ccd5e3);
+ background: var(--Linkbrary-gray10, #e7effb);
+`;
+
+const Text = styled.p`
+ color: var(--Linkbrary-gray100, #373740);
+ font-size: 14px;
+ font-weight: 400;
+`;
+
+const SocialLinks = styled.div`
+ display: flex;
+ gap: 16px;
+`;
+
+function SocialBox({ text }: { text: string }) {
+ return (
+
+ {text}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default SocialBox;
diff --git a/src/pages/signin/index.tsx b/src/pages/signin/index.tsx
index 2f21d8843c..b3f82640db 100644
--- a/src/pages/signin/index.tsx
+++ b/src/pages/signin/index.tsx
@@ -4,10 +4,14 @@ import styled from "styled-components";
import SubmitBtn from "@/src/components/signinPage/SubmitBtn";
import LogoBox from "@/src/components/signinPage/LogoBox";
import { useForm } from "react-hook-form";
+import SocialBox from "@/src/components/signinPage/SocialBox";
+import ErrorMesage from "@/src/components/signinPage/ErrorMesage";
+import { loginRequest } from "@/src/utils/Api";
+import { useRouter } from "next/router";
const BackGround = styled.div`
width: 100%;
- padding: 150px 0px 253px 0px;
+ padding: 100px 0px 200px 0px;
margin: 0 auto;
background: var(--Linkbrary-bg, #f0f6ff);
`;
@@ -18,33 +22,90 @@ const Layout = styled.div`
flex-direction: column;
align-items: center;
margin: 0 auto;
+ gap: 30px;
+
+ @media (max-width: 767px) {
+ width: 325px;
+ }
`;
const Form = styled.form`
+ width: 100%;
display: flex;
flex-direction: column;
gap: 30px;
`;
+
function SigninPage() {
+ const router = useRouter();
+
+ const {
+ register,
+ formState: { errors },
+ handleSubmit,
+ setError,
+ } = useForm({ mode: "onBlur" });
+
+ const emailValidation = register("userEmail", {
+ required: {
+ value: true,
+ message: "이메일을 입력해 주세요",
+ },
+ pattern: {
+ value: /^\S+@\S+$/i,
+ message: "올바른 이메일 주소가 아닙니다.",
+ },
+ });
+
+ const passwordValidation = register("userPw", {
+ required: {
+ value: true,
+ message: "비밀번호를 입력해 주세요",
+ },
+ });
+
+ const onSubmit = async (data: any) => {
+ const res = await loginRequest(data);
+
+ if (res) {
+ const { data } = res;
+ window.localStorage.setItem("accessToken", data.accessToken);
+ router.push("/folder");
+ } else {
+ setError("userEmail", { message: "이메일을 확인해주세요" });
+ setError("userPw", { message: "비밀번호를 확인해주세요" });
+ }
+ };
+
return (
-
-
+
);
diff --git a/src/pages/signup/index.tsx b/src/pages/signup/index.tsx
new file mode 100644
index 0000000000..96202fe452
--- /dev/null
+++ b/src/pages/signup/index.tsx
@@ -0,0 +1,131 @@
+import React from "react";
+import Input from "@/src/components/signinPage/Input";
+import styled from "styled-components";
+import SubmitBtn from "@/src/components/signinPage/SubmitBtn";
+import LogoBox from "@/src/components/signinPage/LogoBox";
+import { useForm } from "react-hook-form";
+import SocialBox from "@/src/components/signinPage/SocialBox";
+import { chechEmail, signupRequest } from "@/src/utils/Api";
+import { useRouter } from "next/router";
+
+const BackGround = styled.div`
+ width: 100%;
+ padding: 80px 0px 180px 0px;
+ margin: 0 auto;
+ background: var(--Linkbrary-bg, #f0f6ff);
+`;
+
+const Layout = styled.div`
+ width: 400px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 0 auto;
+ gap: 30px;
+
+ @media (max-width: 767px) {
+ width: 325px;
+ }
+`;
+
+const Form = styled.form`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+`;
+
+function SignupPage() {
+ const router = useRouter();
+
+ const {
+ register,
+ formState: { errors },
+ handleSubmit,
+ watch,
+ } = useForm({ mode: "onBlur" });
+
+ const emailValidation = register("userEmail", {
+ required: {
+ value: true,
+ message: "이메일을 입력해 주세요",
+ },
+ pattern: {
+ value: /^\S+@\S+$/i,
+ message: "올바른 이메일 주소가 아닙니다.",
+ },
+ validate: async (value) => {
+ const res = await chechEmail(value);
+ if (!res) return "이미 존재하는 이메일입니다.";
+ },
+ });
+
+ const passwordValidation = register("userPw", {
+ required: {
+ value: true,
+ message: "비밀번호를 입력해 주세요",
+ },
+ pattern: {
+ value: /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/,
+ message: "비밀번호는 영문, 숫자 조합 8자 이상 입력해 주세요.",
+ },
+ });
+
+ const passwordCheckValidation = register("userPwCh", {
+ required: {
+ value: true,
+ message: "비밀번호를 입력해 주세요",
+ },
+ validate: (value) => {
+ if (watch("userPw") !== value) return "비밀번호가 일치하지 않아요.";
+ },
+ });
+
+ const onSubmit = async (data: any) => {
+ const res = await signupRequest(data);
+
+ if (res) {
+ const { data } = res;
+ window.localStorage.setItem("accessToken", data.accessToken);
+ router.push("/signin");
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export default SignupPage;
diff --git a/src/utils/Api.ts b/src/utils/Api.ts
index e30ea60cbe..ca93d93dea 100644
--- a/src/utils/Api.ts
+++ b/src/utils/Api.ts
@@ -1,3 +1,4 @@
+import axios from "axios";
import { LinkType } from "../interface/types";
const BASE_URL = "https://bootcamp-api.codeit.kr/api/";
@@ -98,3 +99,75 @@ export async function searchFolderLink(
console.log(e);
}
}
+
+interface FormdataType {
+ userEmail: string;
+ userPw: string;
+}
+
+export async function loginRequest(formdata: FormdataType) {
+ try {
+ const response = await axios.post(`${BASE_URL}sign-in`, {
+ email: formdata.userEmail,
+ password: formdata.userPw,
+ });
+
+ if (response.status === 200) {
+ const res = response.data;
+ return res;
+ } else if (response.status === 400) {
+ console.log("없는 유저 정보");
+ } else if (response.status === 500) {
+ console.log("서버에러");
+ }
+ } catch (error) {
+ // 네트워크 연결 오류 처리
+ console.error("Network error:", error);
+ return null;
+ }
+}
+
+export async function signupRequest(formdata: FormdataType) {
+ try {
+ const response = await axios.post(`${BASE_URL}sign-in`, {
+ email: formdata.userEmail,
+ password: formdata.userPw,
+ });
+
+ if (response.status === 200) {
+ const res = response.data;
+ return res;
+ } else if (response.status === 400) {
+ console.log("잘 못 된 정보를 입력했습니다.");
+ } else if (response.status === 500) {
+ console.log("서버에러");
+ }
+ } catch (error) {
+ // 네트워크 연결 오류 처리
+ console.error("Network error:", error);
+ return null;
+ }
+}
+
+export async function chechEmail(email: string) {
+ try {
+ const response = await axios.post(`${BASE_URL}check-email`, {
+ email: email,
+ });
+
+ if (response.status === 200) {
+ const res = response.data;
+ return res;
+ } else if (response.status === 400) {
+ console.log("잘 못 된 정보를 입력했습니다.");
+ } else if (response.status === 409) {
+ console.log("이메일이 중복되었습니다.");
+ } else if (response.status === 500) {
+ console.log("서버에러");
+ }
+ } catch (error) {
+ // 네트워크 연결 오류 처리
+ console.error("Network error:", error);
+ return null;
+ }
+}