Skip to content
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

[5주차/제리] 워크북 제출합니다 #68

Open
wants to merge 1 commit into
base: jerry/main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion mission/chapter05/mission_ch05/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
72 changes: 38 additions & 34 deletions mission/chapter05/mission_ch05/src/hooks/use-form.js
Original file line number Diff line number Diff line change
@@ -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 };
export default useForm;
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
37 changes: 28 additions & 9 deletions mission/chapter05/mission_ch05/src/pages/Default/Login/login.jsx
Original file line number Diff line number Diff line change
@@ -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 = () => {
Expand All @@ -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 (
<L.LoginBox>
<L.LoginContainer>
<L.LoginContainer onSubmit={onSubmit}>
<h1>로그인</h1>
<L.Input error={login.touched.email && login.errors.email}
type={'email'}
placeholder={'이메일을 입력해주세요!'}
<L.Input
$error={login.touched.email && login.errors.email}
type="email"
placeholder="이메일을 입력해주세요!"
{...login.getTextInputProps('email')}/>

{login.touched.email && login.errors.email
&& <L.ErrorText>{login.errors.email}</L.ErrorText>}

<L.Input error={login.touched.password && login.errors.password}
type={'password'}
placeholder={'비밀번호를 입력해주세요!'}
<L.Input $error={login.touched.password && login.errors.password}
type="password"
placeholder="비밀번호를 입력해주세요!"
{...login.getTextInputProps('password')}/>

{login.touched.password && login.errors.password
&& <L.ErrorText>{login.errors.password}</L.ErrorText>}

<L.Button onClick={handlePressLogin}>로그인</L.Button>
<L.Button
onClick={handlePressLogin}
disabled={!isFormValid}>
로그인
</L.Button>
</L.LoginContainer>
</L.LoginBox>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,35 +21,38 @@ export const LoginContainer = styled.div`
`;

export const Input = styled.input`
width: 90%;
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'};
border: ${props => (props.$error ?
'2px solid red' : '1px solid #ccc')};

&:focus { // input 창에 focus 되면 입력창의 테두리 색 변경
border-color: #007bff;
}
`;

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')};
}
`;

Expand Down
93 changes: 93 additions & 0 deletions mission/chapter05/mission_ch05/src/pages/Default/Signup/signup.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<S.SignupBox>
<S.SignupContainer
onSubmit={handleSubmit(onSubmit)}>
<h1>회원가입</h1>
<S.Input
type="email"
placeholder="이메일을 입력해주세요!"
{...register('email')}
$error={errors.email}
/>
<S.ErrorText>{errors.email?.message}</S.ErrorText>

<S.Input
type="password"
placeholder="비밀번호를 입력해주세요!"
{...register('password')}
$error={errors.password}
/>
<S.ErrorText>{errors.password?.message}</S.ErrorText>

<S.Input
type="password"
placeholder="비밀번호를 한 번 더 입력해주세요!"
{...register('passwordConfirm')}
$error={errors.passwordConfirm}
disabled={!isPasswordValid}
/>
<S.ErrorText>{errors.passwordConfirm?.message}</S.ErrorText>

<S.Button type="submit" disabled={!isValid}>
제출
</S.Button>
</S.SignupContainer>
</S.SignupBox>
);
};

export default Signup;
Original file line number Diff line number Diff line change
@@ -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;
`;
Loading