From a9603cca8c242dff00e46d722a2c375e2e5d14b1 Mon Sep 17 00:00:00 2001 From: gaaahee Date: Thu, 14 Nov 2024 18:38:00 +0900 Subject: [PATCH] =?UTF-8?q?mission=20:=205=EC=A3=BC=EC=B0=A8=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mission/chapter05/mission_ch05/src/App.jsx | 2 +- .../mission_ch05/src/hooks/use-form.js | 72 +++++++------- .../pages/CategoryList/CategoryList.style.jsx | 2 +- .../src/pages/Default/Login/login.jsx | 37 ++++++-- .../src/pages/Default/Login/login.style.jsx | 19 ++-- .../src/pages/Default/Signup/signup.jsx | 93 +++++++++++++++++++ .../src/pages/Default/Signup/signup.style.jsx | 59 ++++++++++++ .../mission_ch05/src/pages/Default/signup.jsx | 47 ---------- .../mission_ch05/src/utils/validate.js | 1 - 9 files changed, 231 insertions(+), 101 deletions(-) create mode 100644 mission/chapter05/mission_ch05/src/pages/Default/Signup/signup.jsx create mode 100644 mission/chapter05/mission_ch05/src/pages/Default/Signup/signup.style.jsx delete mode 100644 mission/chapter05/mission_ch05/src/pages/Default/signup.jsx diff --git a/mission/chapter05/mission_ch05/src/App.jsx b/mission/chapter05/mission_ch05/src/App.jsx index ae1218b..23ef283 100644 --- a/mission/chapter05/mission_ch05/src/App.jsx +++ b/mission/chapter05/mission_ch05/src/App.jsx @@ -3,7 +3,7 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom"; import RootLayout from "./layout/root-layout"; import HomePage from "./pages/Default/home"; import LoginPage from "./pages/Default/Login/login"; -import SignupPage from "./pages/Default/signup"; +import SignupPage from "./pages/Default/Signup/signup"; import SearchPage from './pages/Default/search'; import CategoryListPage from "./pages/CategoryList/CategoryList"; import MovieRoute from "./pages/MoviePages/MovieRoute"; diff --git a/mission/chapter05/mission_ch05/src/hooks/use-form.js b/mission/chapter05/mission_ch05/src/hooks/use-form.js index c64413c..96033e1 100644 --- a/mission/chapter05/mission_ch05/src/hooks/use-form.js +++ b/mission/chapter05/mission_ch05/src/hooks/use-form.js @@ -1,39 +1,43 @@ -// +// 입력 필드 값을 관리하고 유효성 검사를 수행 import { useEffect, useState } from "react"; function useForm({initialValue, validate}) { - const [values, setValue] = useState(initialValue); - const [touched , setTouched] = useState({}); - const [errors , setErrors] = useState({}); - - const handleChangeInput = (name, value) => { - setValue({ - ...value, - [name]: value - }); - }; - - const handleBlur = (name) => { - setTouched({ - ...touched, - [name]: true - }); - }; - - const getTextInputProps = (name) => { - const value = values[name]; - const onChange = (event) => handleChangeInput(name, event.target.value); - const onBlur = () => handleBlur(name); - - return [value, onChange, onBlur]; - }; - - useEffect(() => { - const newErrors = validate(values); - setErrors(newErrors); - }, [validate, values]); - - return {values, errors, touched, getTextInputProps}; + const [values, setValues] = useState(initialValue); + const [touched, setTouched] = useState({}); + const [errors, setErrors] = useState({}); + + const handleChangeInput = (name, value) => { + setValues({ + ...values, + [name]: value, + }); + } + + const handleBlur = (name) => { + setTouched({ + ...touched, + [name]: true, + }); + } + + const getTextInputProps = (name) => { + const value = values[name]; + const onChange = (e) => { + handleChangeInput(name, e.target.value); + } + const onBlur = () => { + handleBlur(name); + } + + return { value, onChange, onBlur }; + } + + useEffect(() => { + const newErrors = validate(values); + setErrors(newErrors); + }, [validate, values]); + + return { values, errors, touched, getTextInputProps }; } -export { useForm }; \ No newline at end of file +export default useForm; \ No newline at end of file diff --git a/mission/chapter05/mission_ch05/src/pages/CategoryList/CategoryList.style.jsx b/mission/chapter05/mission_ch05/src/pages/CategoryList/CategoryList.style.jsx index b5ccc9d..3f01b8d 100644 --- a/mission/chapter05/mission_ch05/src/pages/CategoryList/CategoryList.style.jsx +++ b/mission/chapter05/mission_ch05/src/pages/CategoryList/CategoryList.style.jsx @@ -17,7 +17,7 @@ export const CategoryHeaderName = styled.div` export const CategoryBox = styled.div` display: flex; flex-direction: row; - justifyContent: center; + justify-content: center; gap: 20px; flex-wrap: wrap; margin-top: 20px; diff --git a/mission/chapter05/mission_ch05/src/pages/Default/Login/login.jsx b/mission/chapter05/mission_ch05/src/pages/Default/Login/login.jsx index 0401a29..d9c5986 100644 --- a/mission/chapter05/mission_ch05/src/pages/Default/Login/login.jsx +++ b/mission/chapter05/mission_ch05/src/pages/Default/Login/login.jsx @@ -1,6 +1,6 @@ import React from 'react'; import * as L from './login.style'; -import { useForm } from '../../../hooks/use-form'; +import useForm from '../../../hooks/use-form'; import { validateLogin } from '../../../utils/validate'; const Login = () => { @@ -17,24 +17,43 @@ const Login = () => { console.log(login.values.email, login.values.password); } + + const onSubmit = (event) => { // 폼 제출 시 호출, 에러가 없으면 폼 데이터 출력 + event.preventDefault(); + if (!login.errors.email && !login.errors.password) { + console.log('폼 데이터 제출'); + console.log(login.values); + } + }; + + const isFormValid = !login.errors.email && !login.errors.password && login.values.email && login.values.password; + return ( - +

로그인

- {login.touched.email && login.errors.email && {login.errors.email}} - + + {login.touched.password && login.errors.password + && {login.errors.password}} - 로그인 + + 로그인 +
); diff --git a/mission/chapter05/mission_ch05/src/pages/Default/Login/login.style.jsx b/mission/chapter05/mission_ch05/src/pages/Default/Login/login.style.jsx index bdef994..7a60528 100644 --- a/mission/chapter05/mission_ch05/src/pages/Default/Login/login.style.jsx +++ b/mission/chapter05/mission_ch05/src/pages/Default/Login/login.style.jsx @@ -9,7 +9,7 @@ export const LoginBox = styled.div` background-color: #000; `; -export const LoginContainer = styled.div` +export const LoginContainer = styled.form` display: flex; flex-direction: column; align-items: center; @@ -21,7 +21,7 @@ export const LoginContainer = styled.div` `; export const Input = styled.input` - width: 90%; + width: 80%; padding: 10px; margin-bottom: 15px; font-size: 16px; @@ -29,8 +29,8 @@ export const Input = styled.input` outline: none; // error가 발생하면 테두리 색을 빨간색으로 변경 - border: ${props => props.error ? - '2px solid red' : '1px solid #ccc'}; + border: ${props => (props.$error ? + '2px solid red' : '1px solid #ccc')}; &:focus { // input 창에 focus 되면 입력창의 테두리 색 변경 border-color: #007bff; @@ -38,18 +38,21 @@ export const Input = styled.input` `; export const Button = styled.button` - width: 100%; + width: 85%; padding: 10px; font-size: 16px; color: #fff; - background-color: #ff4d6d; + background-color: ${props => (props.disabled ? + '#ccc' : '#ff4d6d')}; border: none; border-radius: 4px; - cursor: pointer; + cursor: ${props => (props.disabled ? + 'not-allowed' : 'pointer')}; transition: background-color 0.3s ease; &:hover { - background-color: #ff2f4e; + background-color: ${props => (props.disabled ? + '#ccc' : '#ff2f4e')}; } `; diff --git a/mission/chapter05/mission_ch05/src/pages/Default/Signup/signup.jsx b/mission/chapter05/mission_ch05/src/pages/Default/Signup/signup.jsx new file mode 100644 index 0000000..6e5f451 --- /dev/null +++ b/mission/chapter05/mission_ch05/src/pages/Default/Signup/signup.jsx @@ -0,0 +1,93 @@ +import React from 'react'; +import {useForm} from 'react-hook-form' +import * as yup from 'yup' +import {yupResolver} from '@hookform/resolvers/yup' +import * as S from './signup.style'; + +const Signup = () => { + const schema = yup.object().shape({ + email: yup + .string() + .email('유효한 이메일 형식이어야 합니다.') + .required( + '이메일을 반드시 입력해주세요.' + ), + password: yup + .string() + .min( + 8, + '비밀번호는 8자 이상이어야 합니다.' + ).max(16, + '비밀번호는 16자 이하여야 합니다.' + ).required( + '비밀번호를 입력해주세요.' + ), + passwordConfirm: yup + .string() + .oneOf( + [yup.ref('password'), null], + '비밀번호가 일치하지 않습니다.' + ) + .required( + '비밀번호 확인은 필수 입력사항입니다.' + ) + }); + + const { + register, + handleSubmit, + formState: { errors, isValid }, + watch, + } = useForm({ + resolver: yupResolver(schema), + mode: 'onChange' + }); + + const onSubmit = (data) => { + console.log('폼 데이터 제출'); + console.log(data); + }; + + // password 필드의 값이 존재하고 에러가 없으면 true + const passwordValue = watch('password'); + const isPasswordValid = passwordValue && !errors.password; + + return ( + + +

회원가입

+ + {errors.email?.message} + + + {errors.password?.message} + + + {errors.passwordConfirm?.message} + + + 제출 + +
+
+ ); +}; + +export default Signup; \ No newline at end of file diff --git a/mission/chapter05/mission_ch05/src/pages/Default/Signup/signup.style.jsx b/mission/chapter05/mission_ch05/src/pages/Default/Signup/signup.style.jsx new file mode 100644 index 0000000..6db67d3 --- /dev/null +++ b/mission/chapter05/mission_ch05/src/pages/Default/Signup/signup.style.jsx @@ -0,0 +1,59 @@ +import styled from 'styled-components'; + +export const SignupBox = styled.div` + display: flex; + justify-content: center; + align-items: flex-start; // 회원가입 영역을 화면 상단에 배치 + height: 100vh; + padding-top: 10vh; + background-color: #000; +`; + +export const SignupContainer = styled.form` + display: flex; + flex-direction: column; + align-items: center; + width: 400px; + padding: 40px; + background-color: #000; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); +`; + +export const Input = styled.input` + width: 80%; + padding: 10px; + margin-bottom: 15px; + font-size: 16px; + border-radius: 4px; + outline: none; + + // error가 발생하면 테두리 색을 빨간색으로 변경 + border: ${props => (props.$error ? '2px solid red' : '1px solid #ccc')}; + + &:focus { // input 창에 focus 되면 입력창의 테두리 색 변경 + border-color: #007bff; + } +`; + +export const Button = styled.button` + width: 85%; + padding: 10px; + font-size: 16px; + color: #fff; + background-color: ${props => (props.disabled ? '#ccc' : '#ff4d6d')}; + border: none; + border-radius: 4px; + cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')}; + transition: background-color 0.3s ease; + + &:hover { + background-color: ${props => (props.disabled ? '#ccc' : '#ff2f4e')}; + } +`; + +export const ErrorText = styled.p` + color: red; + font-size: 12px; + margin-bottom: 10px; +`; \ No newline at end of file diff --git a/mission/chapter05/mission_ch05/src/pages/Default/signup.jsx b/mission/chapter05/mission_ch05/src/pages/Default/signup.jsx deleted file mode 100644 index 51393f6..0000000 --- a/mission/chapter05/mission_ch05/src/pages/Default/signup.jsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import {useForm} from 'react-hook-form' -import * as yup from 'yup' -import {yupResolver} from '@hookform/resolvers/yup' -import styled from 'styled-components'; - -const Signup = () => { - const schema = yup.object().shape({ - email: yup.string().email().required( - '이메일을 반드시 입력해주세요.' - ), - password: yup.string().min( - 8, - '비밀번호는 8자 이상이어야 합니다.' - ).max(16, '비밀번호는 16자 이하여야 합니다.').required(), - }); - - const {register, handleSubmit, formState: {errors}} = useForm({ - resolver: yupResolver(schema) - }); - - const onSubmit = (data) => { - console.log('폼 데이터 제출') - console.log(data); - } - - return ( - <> -

회원가입

-
- -

{errors.email?.message}

- -

{errors.password?.message}

- -
- - ); -}; - -const inputEmail = styled.input` - border: 1px solid black; - border-radius: 5px; - padding: 5px; - margin: 5px; -`; -export default Signup; \ No newline at end of file diff --git a/mission/chapter05/mission_ch05/src/utils/validate.js b/mission/chapter05/mission_ch05/src/utils/validate.js index 4d386fc..5a034d1 100644 --- a/mission/chapter05/mission_ch05/src/utils/validate.js +++ b/mission/chapter05/mission_ch05/src/utils/validate.js @@ -1,5 +1,4 @@ // 입력받은 email, password 값에 따라 유효성 검사 수행 -import React from 'react'; // @ 앞 : 이메일의 사용자명이 영어 대소문자, 숫자 // @ 뒤 : 도메인 이름이 영어 대소문자, 숫자