기타/마켓컬리 클론코딩

[React/Node] 마켓컬리 클론코딩 9 - 회원가입 페이지

JungCw 2024. 4. 23. 19:27
  • 회원가입 페이지

 

다른 페이지를 손보려다가 아무래도 로그인이 된 상태를 기반으로 하는 것이 좋을거같다고 판단했다.

회원정보를 토대로 로그인하는 것은 구현해두었으니, 회원 정보를 만드는 회원가입 페이지를 생성하였다.

 

<SignUp.js>

import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from 'react-redux';
import { fetchUsers } from '../redux/actions/userAcions';
import { useNavigate } from "react-router-dom";
import axios from "axios";

import "../css/SignUp.css";

const SignUp = () => {
    const navigate = useNavigate();
    const [userData, setUserData] = useState({
        user_id: '',
        user_pw: '',
        nickname: '',
        email: ''
    });

    const dispatch = useDispatch();
    const userlist = useSelector(state => state.users.users);

    useEffect(() => {
        dispatch(fetchUsers());
    }, []);

    const change = (e) => {
        setUserData({...userData, [e.target.name]: e.target.value });
    }

    const check_ID = () => {
        const state = userlist.find(user => user.user_id === userData.user_id)
        if(state){
            alert('중복된 아이디입니다.');
            setUserData({...userData, user_id: ''});
        } else{
            alert('중복되지 않은 아이디입니다.')
        }
    }

    const submit = async (e) => {
        const config = {"Content-Type": 'application/json'};
        e.preventDefault();
        try {
            const response = await axios.post('http://localhost:8000/api/signup', userData, config);
            console.log(response.data);
            alert('등록완료');
            navigate("/login");
        } catch (error) {
            console.error(error);
        }
    }

    return (
        <signup>
            <div className="signup_wrap">
                <h2>회원가입</h2>
                <div className="form_top">
                    <span className="star">*</span>
                    필수입력사항
                </div>
                <div className="signup_form">
                    <form onSubmit={submit}>
                        <div className="signup_info">
                            <div className="signup_id">
                                <div className="title">아이디<span className="star">*</span></div>
                                <div className="id info">
                                    <input name="user_id" type="text" placeholder="아이디를 입력해주세요" 
                                    value={userData.user_id} onChange={change} />
                                </div>
                                <div className="check_button">
                                    <input className="bt_style" type="button" value="중복확인" onClick={() => check_ID()} />
                                </div>
                            </div>
                            <div className="signup_pw">
                                <div className="title">비밀번호<span className="star">*</span></div>
                                <div className="pw info">
                                    <input name="user_pw" type="text" placeholder="비밀번호를 입력해주세요" 
                                    onChange={change} />
                                </div>
                            </div>
                            <div className="signup_pw_check">
                                <div className="title">비밀번호확인<span className="star">*</span></div>
                                <div className="pw_check info">
                                    <input type="text" placeholder="비밀번호를 한번 더 입력해주세요" />
                                </div>
                            </div>
                            <div className="signup_name">
                                <div className="title">이름<span className="star">*</span></div>
                                <div className="name info">
                                    <input name="nickname" type="text" placeholder="이름을 입력해주세요" 
                                    onChange={change} />
                                </div>
                            </div>
                            <div className="signup_email">
                                <div className="title">이메일<span className="star">*</span></div>
                                <div className="email info">
                                    <input name="email" type="email" placeholder="예: marketkurly@kurly.com" 
                                    onChange={change} />
                                </div>
                                <div className="check_button">
                                    <input className="bt_style" type="button" value="중복확인" />
                                </div>
                            </div>
                            <div className="signup_ph">
                                <div className="title">휴대폰<span className="star">*</span></div>
                                <div className="ph info">
                                    <input type="text" placeholder="숫자만 입력해주세요" />
                                </div>
                                <div className="check_button">
                                    <input className="bt_style" type="button" value="인증번호 받기" />
                                </div>
                            </div>
                            <div className="signup_address">
                                <div className="title">주소<span className="star">*</span></div>
                                <div className="address info">
                                    <input className="bt_style" type="button" value="주소 검색" />
                                </div>
                            </div>
                            <div className="address_plus">
                                <div className="title"></div>
                                <div className="info">배송지에 따라 상품 정보가 달라질 수 있습니다.</div>
                            </div>
                            <div className="signup_gender">
                                <div className="title">성별</div>
                                <div className="gender">
                                    <div className="man"><input type="radio" name="gender" value="남자" /><span>남자</span></div>
                                    <div className="woman"><input type="radio" name="gender" value="여자" /><span>여자</span></div>
                                    <div className="none"><input type="radio" name="gender" value="선택안함" checked /><span>선택안함</span></div>
                                </div>
                            </div>
                            <div className="signup_birth">
                                <div className="title">생년월일</div>
                                <div className="birth_date info"> {/* input date랑 다르다고 판단. number의 글자수 제한 안됨. */}
                                    <input type="number" placeholder="YYYY" /><div>/</div>
                                    <input type="number" placeholder="MM" /><div>/</div>
                                    <input type="number" placeholder="DD" />
                                </div>
                            </div>
                        </div>
                        <div className="terms">
                            <div className="title">
                                이용약관동의
                                <span className="star">*</span>
                            </div>
                            <div className="terms_body">
                                <div className="fir">
                                    <input type="checkbox" id="check1"/>
                                      <label for="check1"></label>
                                    <span>전체 동의합니다.</span>
                                    <p>선택항목에 동의하지 않은 경우도 회원가입 및 일반적인 서비스를 이용할 수 있습니다.</p>
                                </div>
                                <div className="sec">
                                    <input type="checkbox" id="check2"/>
                                      <label for="check2"></label>
                                    <span> 이용약관 동의</span>
                                    <span className="sub">(필수)</span>
                                </div>
                                <div className="sec">
                                    <input type="checkbox" id="check3"/>
                                      <label for="check3"></label>
                                    <span> 개인정보 수집·이용 동의</span>
                                    <span className="sub">(필수)</span>
                                </div>
                                <div className="sec">
                                    <input type="checkbox" id="check4"/>
                                      <label for="check4"></label>
                                    <span> 개인정보 수집·이용 동의</span>
                                    <span className="sub">(선택)</span>
                                </div>
                                <div className="thr">
                                    <input type="checkbox" id="check5"/>
                                      <label for="check5"></label>
                                    <span> 무료배송, 할인쿠폰 등 혜택/정보 수신 동의</span>
                                    <span className="sub">(선택)</span>
                                    <div className="thr_sub">
                                        <input type="checkbox" id="check6"/>
                                          <label for="check6"></label>
                                        <span>SMS</span>
                                        <input type="checkbox" id="check7"/>
                                          <label for="check7"></label>
                                        <span>이메일</span>
                                    </div>
                                </div>
                                <div className="sec">
                                    <input type="checkbox" id="check8"/>
                                      <label for="check8"></label>
                                    <span> 본인은 만 14세 이상입니다.</span>
                                    <span className="sub">(필수)</span>
                                </div>
                            </div>
                        </div>
                        <div className="signup_bt_wrap">
                            <input type="submit" value="가입하기" />
                        </div>
                    </form>
                </div>
            </div>
        </signup>
    )
}

export default SignUp;

 

일단은 이름, 아이디, 비밀번호, 이메일, 아이디 중복확인만 구현해두었고, 필요한 css만 손보았다.

아직 캘린더나 주소, 인증 등은 미구현이다.

 

이번에 서버로 데이터를 보내는 작업을 하면서 알게되었는데, express 4버전 이상부터는 bodyparser가 필요없다고 한다.

그리고 DB에서 뭔가 불러오거나 저장할 때에는 정확히 어떤 스키마인지 명시하도록 해야겠다.

아마도 데이터베이스 안에 다른 스키마가 존재해서 그런거같은데, 이거 하나때문에 한참 헤맸다.

INSERT INTO kurly.user (user_id, user_pw, nickname, email) VALUES (?, ?, ?, ?);

이런 식으로 테이블 앞에 스키마를 붙이는 (kurly.user) 작업을 생각해야겠다.

 

 

<SignUp.css>

.star {
    color: orangered;
}

.signup_wrap {
    padding: 80px 30% 0 30%;
    width: 40%;
    font-size: 14px;
    font-weight: 600;
}

h2 {
    font-size: 25px;
    text-align: center;
}

.form_top {
    font-size: 12px;
    text-align: right;
    padding: 10px;
    color: rgb(96, 96, 96);
    border-bottom: 2px solid black;
}

.signup_info {
    padding-bottom: 25px;
    border-bottom: 1px solid black;
}

.signup_info div {
    display: flex;
}

.title {
    width: 22%;
    height: 50px;
    margin: 2% 1%;
    align-items: center;
}

.info {
    width: 50%;
    height: 50px;
    margin: 2% 1%;

    input {
        width: 100%;
        border-radius: 5px;
        border-width: 1px;
        border-color: rgb(180, 180, 180);
    }

    input::placeholder {
        padding: 0 15px;
        font-size: 15px;
    }
}

.check_button {
    width: 21%;
    height: 50px;
    margin: 2% 1%;
}

.bt_style {
    width: 100%;
    border-radius: 5px;
    background-color: transparent;
    border-color: rgb(111, 0, 128);
    border-width: 1px;
    color: rgb(111, 0, 128);
    font-weight: 600;
}

.address .bt_style {
    border-color: rgb(111, 0, 128);
}

.address_plus {
    div {
        height: 20px;
        margin-top: 0;
    }

    .info {
        font-weight: 500;
        font-size: 12px;
    }
}

.gender {
    width: 50%;
    height: 50px;
    margin: 2% 1%;
    align-items: center;

    div {
        width: 33%;
    }
}

[type="radio"] {
    vertical-align: middle;
    appearance: none;
    border: max(1px, 0.1em) solid rgb(194, 194, 194);
    border-radius: 50%;
    width: 20px;
    height: 20px;
    margin-right: 5px;
}

[type="radio"]:checked {
    border-color: rgb(111, 0, 128);
    border-width: 5px;
}

.birth_date {
    border: 1px solid rgb(180, 180, 180);
    border-radius: 5px;
    align-items: center;

    div {
        font-size: 17px;
        font-weight: 500;
        color: rgb(180, 180, 180);
    }

    input {
        border: none;
        text-align: center;
        height: 20px;
    }

    input::placeholder {
        font-size: 14px;
        text-align: center;
    }

    input:focus {
        outline: none;
    }
}

.terms {
    display: flex;
    padding-top: 25px;
}

.terms .terms_body div {
    width: 100%;
    margin: 2% 1%;
}

span {
    display: inline-block;
    text-indent: 10px;
    vertical-align: top;
    font-size: 14px;
}

.fir {
    height: 50px;

    span {
        font-size: 20px;
    }

    p {
        font-size: 11px;
        color: rgb(96, 96, 96);
        font-weight: 500;
        text-indent: 35px;
    }
}

.sec {
    color: rgb(96, 96, 96);
    height: 30px;

    label {
        top: -4px;
    }
}

.thr {
    color: rgb(96, 96, 96);
    height: 70px;

    label {
        top: -4px;
    }
}

.thr .thr_sub {
    padding-top: 3px;

    label {
        margin-left: 40px;
    }
}

input[type="checkbox"] {
    display: none;
}

input[type="checkbox"]+label {
    display: inline-block;
    width: 20px;
    height: 20px;
    border: 2px solid rgb(180, 180, 180);
    border-radius: 50%;
    position: relative;
}

input[type="checkbox"]:checked+label::after {
    content: '✔';
    font-size: 15px;
    width: 20px;
    height: 20px;
    text-align: center;
    position: absolute;
    left: 0;
    right: 0;
}

.signup_bt_wrap {
    text-align: center;
    padding: 30px 0 60px 0;
    input {
        border: 1px solid rgb(216, 216, 216);
        background-color: rgb(111, 0, 128);
        font-size: 15px;
        font-weight: 600;
        color: white;
        width: 40%;
        height: 55px;
        border-radius: 4px;
    }
}

 

 

<signupHandler.js>

const db = require('./DBinfo');

const postSignupList = (req, res) => {
    const { user_id, user_pw, nickname, email } = req.body;
    const query = "INSERT INTO kurly.user (user_id, user_pw, nickname, email) VALUES (?, ?, ?, ?);";
    const params = [user_id, user_pw, nickname, email];
    db.query(query, params, (err, result)=>{
        res.send(result);
    })
};

module.exports = postSignupList;

 

여담이지만 코드블럭을 사용하지 않고 그냥 복붙하면 이렇게 코드가 작성되는걸 이제 알았다..

 

회원가입 페이지 다른 기능들은 일단 두고, 다음으로는 로그인 후의 토큰에 대해서 공부를 해볼 생각이다.

 

 

 

https://memo-code.tistory.com/45

 

마켓컬리 클론코딩 10 - 로그인/로그아웃

로그인/로그아웃 (로그아웃 버튼 css를 까먹었다..) 처음해보는 작업이라 그래도 좀 오래 걸릴거라 생각했지만, 예상보다 많이 걸린 작업이었다.온갖 에러와 정보 사이에서 어찌저찌 만들기는

memo-code.tistory.com