2์ฐจ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๊ธฐ์ตํด๋๊ณ ์ถ์ ์ฝ๋๋ค์ ์ ๋ฆฌํด๋ณด์๋ค! ๐
๋ก๊ทธ์ธ๊ณผ ํ์๊ฐ์
์ ์์์ ์ดํด๋ณด๋ฉด ๊ฐ์ input์คํ์ผ์ ์ฌ์ฉํ๋ ๊ฒ์ ์ ์ ์๋ค. input์ ๋ชจ๋ ๊ฐ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ค์ํ๊ณ ์ ์ผํ๊ฒ ํ์๊ฐ์
๋ถ๋ถ๋ง email
์ค๋ณต ๊ฒ์ฌ๋ฅผ ์ค์ํ๋ค. ๊ฐ์ input ํ(์คํ์ผ)์ ์ด์ฉํด login
๊ณผ signup
๋ ๊ฐ์ ๊ฐ์ฒด ์์ input์ ๋ค์ด๊ฐ๋ ๊ฐ๊ฐ์ data๋ฅผ ๋ฐฐ์ด์ผ๋ก ๋ฃ์๋ค.
const INITIAL_INPUT = {
name: '',
birthday: '',
email: '',
emailOk: '',
password: '',
passwordOk: '',
};
function Nav() {
const [showModal, setShowModal] = useState(false);
const [isSignUp, setIsSignUp] = useState(false);
const [loginInput, setLoginInput] = useState(INITIAL_INPUT);
const LOGIN = {
type: 'login',
text: '๋ก๊ทธ์ธ',
data: [
{
type: 'email',
name: 'email',
value: loginInput.email,
text: '์ด๋ฉ์ผ',
holder: '์ด๋ฉ์ผ',
data: '์ ํํ์ง ์์ ์ด๋ฉ์ผ ํ์์
๋๋ค.',
},
{
type: 'password',
name: 'password',
value: loginInput.password,
text: '๋น๋ฐ๋ฒํธ',
holder: '๋น๋ฐ๋ฒํธ',
data: '๋น๋ฐ๋ฒํธ๋ ์๋ฌธ,์ซ์,ํน์๋ฌธ์๋ฅผ ์กฐํฉํ์ฌ 8~12์๋ก ๊ตฌ์ฑํ์ธ์.',
},
],
};
const SIGNUP = {
type: 'signUp',
text: 'ํ์๊ฐ์
',
data: [
{
type: 'name',
name: 'name',
value: loginInput.name,
text: '์ด๋ฆ',
holder: '์ด๋ฆ',
data: '์ ํํ์ง ์์ ์ด๋ฆ์
๋๋ค.',
},
{
type: 'birthday',
name: 'birthday',
value: loginInput.birthday,
text: '์๋
์์ผ 8์๋ฆฌ ex) 1990-05-05',
holder: '์๋
์์ผ',
data: '์ ํํ์ง ์์ ์๋
์์ผ์
๋๋ค.',
},
{
type: 'email',
name: 'emailOk',
value: loginInput.emailOk,
text: '์ด๋ฉ์ผ',
holder: '์ด๋ฉ์ผ',
data: '์ ํํ์ง ์์ ์ด๋ฉ์ผ์
๋๋ค.',
},
{
type: 'password',
name: 'password',
value: loginInput.password,
text: '๋น๋ฐ๋ฒํธ',
holder: '๋น๋ฐ๋ฒํธ',
data: '๋น๋ฐ๋ฒํธ๋ ์๋ฌธ,์ซ์,ํน์๋ฌธ์๋ฅผ ์กฐํฉํ์ฌ 8~12์๋ก ๊ตฌ์ฑํ์ธ์.',
},
{
type: 'password',
name: 'passwordOk',
value: loginInput.passwordOk,
text: '๋น๋ฐ๋ฒํธ ํ์ธ',
holder: '๋น๋ฐ๋ฒํธ๋ฅผ ๋ค์ ํ ๋ฒ ์
๋ ฅํด์ฃผ์ธ์',
data: '๋น๋ฐ๋ฒํธ๋ฅผ ํ ๋ฒ ๋ ํ์ธํด์ฃผ์ธ์.',
},
],
};
...์ค๋ต
state๊ฐ์ผ๋ก ์ง์ ๋ issignup
๊ฐ์ด false์ผ ๋๋ SIGNUP
format์, true์ผ ๋๋ LOGIN
format์ ๋ฐํํ์ฌ ๋ชจ๋ฌ์ ๋ณด์ฌ์ฃผ์๋ค.
return (
<>
<Navigator>
{showModal && (
<Modal
handleModal={handleModal}
onClickWindow={onClickWindow}
isSignUp={isSignUp}
onClickSignUp={onClickSignUp}
setValues={setValues}
inputValue={loginInput}
// format ํ์์ state๊ฐ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋๊ฒจ์ฃผ์๋ค.
format={isSignUp ? SIGNUP : LOGIN}
onSign={onSign}
/>
)}
๋ก๊ทธ์ธ/ํ์๊ฐ์
๋ถ๋ถ์ ๋งก์ผ๋ฉด์ ๊ฐ์ฅ ์ค์ํ๋ค๊ณ ์๊ฐํ ๊ตฌํ ์ฌํญ์ด์๋ค.
์ฌ์ค ์ด๋ฉ์ผ์ด๋ ๋น๋ฐ๋ฒํธ๊ฐ์ ์ ํจ์ฑ ๊ฒ์ฌ๋ ๋ฐฑ ์ชฝ์์ ์งํํ๊ธฐ ๋๋ฌธ์ ํ๋ก ํธ์์๋ ๊ตณ์ด ํ์์๋ ๊ฒ์ด ์ฌ์ค์ด๋ค. ๊ทธ๋ฐ๋ฐ UI/UX
๊ฐ ์ค์์ ๋๋ ์์ฆ์ ์น ์ฌ์ดํธ์์๋ ์ฌ์ฉ์์ ์
๋ ฅ๊ณผ ๋์์ ํด๋น ํญ๋ชฉ์ ์ ํจ์ฑ ๊ฒ์ฌ ๊ฒฐ๊ณผ(input
๋ฐ์ ๋นจ๊ฐ ๊ธ์จ, ๋นจ๊ฐ ๋ฐ์ค, ์ด๋ก ์ฒดํฌ ํ์ ๋ฑ๋ฑ)๋ฅผ ๋ฐ๋ก๋ฐ๋ก ์๊ฐํํ์ฌ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ๋น์ฐ์ ๋๊ณ ์๋ค.
์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ํ์ํ ํญ๋ชฉ๋ค์ ๊ฒ์ฌ ๊ฐ์ ์ ๊ทํํ์ ํ์์ ์์ ๋ฐ์ดํฐ๋ก ๋ง๋ค์๋ค. ๊ฒ์ฌ๋ฅผ ์ค์ํ๋ ํจ์๋ฅผ ๋ง๋ ํ input
์ value
๊ฐ์ ๋ฐ์ ํจ์๋ฅผ ๋๋ฆฐ ํ true
๋ false
๋ก ๋ฐํํ์ฌ input
๋ฐ์ (๋นจ๊ฐ์์) guide text๋ฅผ ๋ณด์ฌ์ฃผ์๋ค.
//์ด๋ฉ์ผ ์ ๊ทํํ์
const emailValid = /^[a-zA-Z\d+-.]+@[a-zA-Z\d+-.]+\.[a-zA-Z]{2,3}$/;
//๋น๋ฐ๋ฒํธ ์ ๊ทํํ์
const pwValid =
/^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&])[A-Za-z\d$@$!%*#?&]{8,}$/;
//์๋
์์ผ ์ ๊ทํํ์
const birthValid =
/^(19[0-9][0-9]|20\d{2})-(0[0-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/;
//์ ํจ์ฑ ๊ฒ์ฌ ํจ์
function Inputs({ format, setValues, inputValue, onSign }) {
const isValidInput = item => {
const { email, emailOk, password, passwordOk, name, birthday } = inputValue;
const INPUT_VALIDATION_MAP = {
name: name === '' || (name && name.length > 1),
birthday: birthday === '' || (birthday && birthValid.test(birthday)),
email: email === '' || (email && emailValid.test(email)),
emailOk: emailOk === '' || (emailOk && emailValid.test(emailOk)),
password: password === '' || (password && pwValid.test(password)),
passwordOk: passwordOk === '' || (passwordOk && passwordOk === password),
};
return INPUT_VALIDATION_MAP[item];
};
return (
<InputAll>
{format.data.map(input => {
return (
<AllInput key={input.id}>
<Label>{input.text}</Label>
<InputOver>
<Input
onChange={e => setValues(e, input)}
name={input.name}
type={input.type}
placeholder={input.holder}
value={
input.name === 'birthday'
? input.value.replace(/(\d{4})(\d{2})(\d{1})/g, '$1-$2-$3')
: input.value
}
/>
{input.name === 'emailOk' && (
<OverlapOk isValidInput={isValidInput}>์ค๋ณต ํ์ธ</OverlapOk>
)}
</InputOver>
<GuideText
name={input.name}
input={input}
//false๋ฅผ ๋ฐํํ๋ฉด guide text๊ฐ ๋ํ๋๋ค.
isValidInput={isValidInput}
/>
</AllInput>
);
})}
<AuthBtn onClick={() => onSign(format)}>{format.text}</AuthBtn>
</InputAll>
);
}
์๋ ์์ผ์ ํ์ดํ(-) ๋ฃ๊ธฐ
๋ก๊ทธ์ธ/ํ์๊ฐ์ ๋ถ๋ถ์ ๋ด๋นํ๋ ๋ฐฑ์๋ ํ์์ด ์๋ ์งํํ๋ ์๋ ์์ผ ๋ฐ์ดํฐ ๋ฐฉ์(900101)์์ ๋ ,์,์ผ ์ฌ์ด์ ํ์ดํ์ ๋ฃ์ ๋ฐฉ์(90-01-01)์ผ๋ก ๋ณ๊ฒฝํด๋ฌ๋ผ๋ ์์ฒญ์ ํ๋ค.
<Input value={ input.name === 'birthday' ? input.value.replace(/(\d{4})(\d{2})(\d{1})/g, '$1-$2-$3') : input.value } />
ํด๋น
input
์ดname ==='birthday'
์ผ ๋ ์ ๊ทํํ์์ ํตํด ํ์ดํ์ ์ถ๊ฐํ๋ค!
customFetch
ํจ์๋ฅผ ๋ง๋ค์ด Nav.js์ ์๋ fetch ํจ์๋ฅผ ์กฐ๊ธ ๋ ๊ฐ๋จํ๊ณ ๊น๋ํ๊ฒ ์งค ์ ์์๋ค.
import { GET_LOGIN } from '../config';
//์ปค์คํ
ํจ์น ํจ์
export const customFetch = (endpoint, options = {}, { onSuccess } = {}) => {
const opts = {
method: 'POST',
...options,
};
return fetch(GET_LOGIN + endpoint, opts)
.then(res => res.json())
.then(res => onSuccess && onSuccess(res));
};
const requestSignIn = loginInput => {
const { email, password } = loginInput;
//์ปค์คํ
ํจ์น ํจ์ ํธ์ถ
customFetch(API.SIGNIN, {
body: JSON.stringify(
{
email: email,
password: password,
},
{
onSuccess: res => {
if (res.token) {
localStorage.setItem('token', res.token);
}
if (res.MESSAGE === 'SUCCESS') {
alert('METABOX์ ์ค์ ๊ฑธ ํ์ํฉ๋๋ค');
handleModal();
} else {
alert('์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ค์ ํ ๋ฒ ํ์ธํด์ฃผ์ธ์');
}
},
}
),
});
};
const onClickWindow = e => {
const clicked = e.target.closest('.modal');
if (clicked) {
return;
}
handleModal();
};
์ฌ๊ธฐ์ closest
๋ ์์ ๋ถํฐ ๋ถ๋ชจ ์์ ๋จ์๋ก ์ถ๋ฐํด , ๊ฐ ์์๊ฐ ์ง์ ํ ์ ํ์์ ๋ง์กฑํ ๋๊น์ง ํ์ํ๋ค. ( ์ถ์ฒ - ๊ณต์๋ฌธ์ )
function Modal({
onClickWindow,
}) {
return (
<Container onClick={onClickWindow}>
<ContainerBox className="modal">
<Form />
</ContainerBox>
</Container>
);
}
๋ฐ๋ก ์ด๋ฒคํธ ์บก์ณ๋ง ๋ฒ๋ธ๋ง ์ง์ ์ ์ํด์คฌ์์ผ๋ก ๋ฒ๋ธ๋ง์ผ๋ก ์งํ์ด ๋๊ณ
cliked
์ <ContainerBox>
์ className= "modal"์ด ๋ค์ด๊ฐcliked
์ <Container>
๊ฐ ๋ค์ด๊ฐcliked๊ฐ ์กด์ฌํ๋ฉด : return; โก๏ธ ๋ชจ๋ฌ ๊ทธ๋๋ก
cliked null์ด๋ฉด : false โก๏ธ handleModal ํจ์ ์คํ(๋ชจ๋ฌ ๋ซ๊ธฐ)
textarea ๊ธ์ ์ ์ ํ
const isCount = e => { const maxText = 50; const textVal = e.target.value; const textLen = textVal.length; setTextCount({ ...textCount, textLen }); setCommentInput({ ...commentInput, comment: textVal }); if (textLen > maxText) { alert('์ต๋ 50์ ๊น์ง๋ง ์ ๋ ฅ๊ฐ๋ฅํฉ๋๋ค.'); } };
๋ถ(minute)์ผ๋ก ๋ฐ์ ์๊ฐ ์๊ฐ, ์ผ๋ก ๋ฐ๊ฟ์ฃผ๋ ํจ์
const timeToday = value => { const minuteTime = value; if (minuteTime < 1) return '๋ฐฉ๊ธ์ '; if (minuteTime < 60) { return `${minuteTime}๋ถ ์ `; } const hourTime = Math.floor(minuteTime / 60); if (hourTime < 24) { return `${hourTime}์๊ฐ ์ `; } const dayTime = Math.floor(minuteTime / 60 / 24); if (dayTime < 365) { return `${dayTime}์ผ ์ `; } return `${Math.floor(dayTime / 365)}๋ ์ `; };
๋ชจ๋ฌ์ฐฝ์ ๋์ ์ ๋ ์ธ๋ถ ์คํฌ๋กค ์ ํ
useEffect(() => { document.body.style.cssText = ` position: fixed; top: -${window.scrollY}px; overflow-y: hidden; width: 100%;`; return () => { const scrollY = document.body.style.top; document.body.style.cssText = ''; window.scrollTo(0, parseInt(scrollY || '0', 10) * -1); }; }, []);