221206 프엔6 포트폴리오 프로젝트(2)

geenee·2022년 12월 6일
0

Portfolio

목록 보기
8/38

DB - 회원 테이블 생성

필수 항목
- 아이디(중복안됨)
- 비밀번호 -> MD5로 암호화 한 값으로 저장
- 이름
- 생년월일
- 이메일(중복안됨)
- 성별
- 닉네임(중복안됨)
- 전화번호(필수아님)
- 회원가입일시
- 상태(정상,탈퇴,접근금지 등?)
- 탈퇴일시

테이블 생성

각 항목에 대한 데이터타입,크기,조건 등 참고한 사이트 -> MySQL 회원테이블 – 내가 자주 사용하는 구조

아이디 찾기 -> 이름/생년월일/이메일
비밀번호 찾기 -> 아이디/이메일 코드 전송
React로 블로그 만들기 29. 아이디 & 비밀번호 찾기 3 (비밀번호 찾기, 이메일 전송 nodemailer)

회원가입 화면 html/css 작성

위시빈, 마이로, 스투비플래너에서 입력받는 항목 참고
페이지 디자인은 위시빈 클론수준으로 복붙..ㅎ
생년월일이나 번호에 누르면 좌라락 나오는거 select box임
hover에 속도 딜레이

Join.css

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-family: Handot;
}

u,
li {
  list-style: none;
  text-decoration: none;
}

button {
  border: 0;
  outline: 0;
  cursor: pointer;
}

.join-container {
  width: 100%;
  height: 100%;
  /* border: solid 1px red; */
}

.join-container .centerfix {
  /* border: solid 1px red; */
  width: 790px;
  height: 100%;
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.join-container .centerfix img {
  margin-top: 32px;
  margin-bottom: 48px;
  cursor: pointer;
}
.join-container .centerfix .title {
  font-size: 1.75rem;
  font-weight: bold;
  margin-bottom: 32px;
}
.join-container .centerfix .userinfo {
  /* border: solid 1px red; */
  width: 500px;
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.join-container .centerfix .userinfo div {
  /* border: solid 1px red; */
  white-space: nowrap;
}

.join-container .centerfix .userinfo div section {
  font-size: 1rem;
  font-weight: bold;
  margin-bottom: 12px;
}

.join-container .centerfix .userinfo div section .essential {
  /* border: solid 1px red; */
  margin-left: 4px;
  font-size: 1rem;
  vertical-align: top;
  font-weight: bold;
  color: #dc3545;
}

.join-container .centerfix .userinfo div .item {
  border: none;
  outline: none;
  border-bottom: solid 1px lightgray;
  width: 100%;
  height: 40px;
  font-size: 0.875rem;
}
.join-container .centerfix .userinfo div .item:focus {
  border-bottom: solid 1px black;
}
.join-container .centerfix .userinfo div .overlap {
  width: 380px;
}

.join-container .centerfix .userinfo div .overlapbtn {
  width: 110px;
  height: 40px;
  background-color: #ffffff;
  border-radius: 7px;
  border: solid 1px black;
  margin-left: 10px;
  font-size: 0.875rem;
  text-align: center;
}

.join-container .centerfix .userinfo div .overlapbtn:hover {
  background-color: rgba(1, 92, 86, 0.1);
}

.join-container .centerfix .userinfo .joinbtn {
  width: 180px;
  height: 45px;
  margin: auto;
  background-color: #015c56;
  color: #ffffff;
  font-size: 1rem;
  border-radius: 7px;
  margin-bottom: 62px;
}

.join-container .centerfix .userinfo .joinbtn:hover {
  background-color: rgba(1, 92, 86, 0.8);
}

Join.js

import React from "react";
import axios from "axios";
import "./Join.css";
import logoimg from "../img/yeohaengGo(logo)_crop.png";

import { useNavigate } from "react-router-dom";

function Join() {
  const navigation = useNavigate();

  const [data, setData] = React.useState({
    id: "",
    nickname: "",
    pw: "",
  });

  const [idcheck, setIdcheck] = React.useState({
    code: "error",
  });

  const valuechange = (event) => {
    const name = event.target.name;
    const cloneData = { ...data };
    cloneData[name] = event.target.value;
    // console.log(cloneData);
    setData(cloneData);
  };

  const joincheck = async () => {
    if (idcheck.code === "error") {
      alert("아이디를 확인하세요!");
      return;
    }
    await axios({
      url: "http://localhost:5000/join",
      method: "POST",
      data: data,
    })
      .then((res) => {
        const { code, message } = res.data;
        if (code === "error") {
          alert(message);
          return;
        }
        navigation("/login");
      })
      .catch((e) => {
        console.log("회원가입 오류!", e);
      });
  };

  const runidcheck = async () => {
    await axios({
      url: "http://localhost:5000/idcheck",
      method: "POST",
      data: { id: data.id },
    })
      .then((res) => {
        const { code, message } = res.data;
        if (code === "error") {
          alert(message);
        }
        setIdcheck({ code: code });
      })
      .catch((e) => {
        console.log("아이디 중복체크 오류!", e);
      });
  };

  return (
    <div className="join-container">
      <div className="centerfix">
        <img
          src={logoimg}
          alt="logo이미지"
          onClick={() => {
            navigation("/main");
          }}
        />
        <span className="title">회원가입</span>
        <div className="userinfo">
          <div className="idcontainer">
            <section>
              <span>아이디</span>
              <span className="essential">*</span>
            </section>
            <input
              type="text"
              name="id"
              placeholder="아이디"
              className="item overlap"
              onChange={valuechange}
            />
            <button className="overlapbtn" onClick={runidcheck}>
              중복확인
            </button>
          </div>
          <div className="pwcontainer">
            <section>
              <span>비밀번호</span>
              <span className="essential">*</span>
            </section>
            <input
              type="password"
              name="pw"
              placeholder="비밀번호"
              className="item"
              onChange={valuechange}
            />
          </div>
          <div className="repwcontainer">
            <section>
              <span>비밀번호 재입력</span>
              <span className="essential">*</span>
            </section>
            <input
              type="password"
              name="repw"
              placeholder="비밀번호 재입력"
              className="item"
              onChange={valuechange}
            />
          </div>
          <div className="namecontainer">
            <section>
              <span>이름</span>
              <span className="essential">*</span>
            </section>
            <input
              type="text"
              name="name"
              placeholder="이름"
              className="item"
              onChange={valuechange}
            />
          </div>
          <div className="birthcontainer">
            <section>
              <span>생년월일</span>
              <span className="essential">*</span>
            </section>
            여기에 select box
          </div>
          <div className="emailcontainer">
            <section>
              <span>이메일</span>
              <span className="essential">*</span>
            </section>
            <input
              type="text"
              name="email"
              placeholder="geenee@gmail.com"
              className="item overlap"
              onChange={valuechange}
            />
            <button className="overlapbtn" onClick={runidcheck}>
              중복확인
            </button>
          </div>
          <div className="sexcontainer">
            <section>
              <span>성별</span>
              <span className="essential">*</span>
            </section>
            여기에 select box
          </div>
          <div className="nicknamecontainer">
            <section>
              <span>닉네임</span>
              <span className="essential">*</span>
            </section>
            <input
              type="text"
              name="nickname"
              placeholder="닉네임"
              className="item overlap"
              onChange={valuechange}
            />
            <button className="overlapbtn" onClick={runidcheck}>
              중복확인
            </button>
          </div>
          <div className="phonecontainer">
            <section>
              <span>전화번호</span>
            </section>
            여기에 select box
          </div>
          <button className="joinbtn" onClick={joincheck}>
            회원가입
          </button>
        </div>
      </div>
    </div>
  );
}

export default Join;

회원가입 화면
회원가입 화면

아직 selct box랑 hover transition 적용 안함~
그리고 js에서도 함수 기능 구현 안되어있음...

로그인 입력 예외처리 구현

[React] 리액트 classnames 활용하기 (classnames, !! 연산자)
1. 비밀번호 8자리 이하인 경우 로그인 안됨
2. 비밀번호 20자 이상인 경우 로그인 안됨
3. 아이디 입력된 글자가 없는 경우 로그인 안됨
4. 입력된 비밀번호는 nodejs에서 md5 함수 이용하여 hash값 생성(비밀번호를 암호화 한 값을 비교한다)

Login.css

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-family: Handot;
}

u,
li {
  list-style: none;
  text-decoration: none;
}

button {
  border: 0;
  outline: 0;
  cursor: pointer;
}

.login-container {
  width: 100%;
  height: 100vh;
}

.login-container .centerfix {
  width: 790px;
  height: 100%;
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.login-container .centerfix > div {
  /* border: solid 1px red; */
  margin-bottom: 36px;
}

.login-container .centerfix .logininfo {
  /* border: solid 1px red; */
  width: 360px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.login-container .centerfix .logininfo img {
  margin-top: 42px;
  margin-bottom: 42px;
  cursor: pointer;
}

.login-container .centerfix .logininfo .title {
  font-size: 1.75rem;
  font-weight: bold;
  margin-bottom: 62px;
}

.login-container .centerfix .logininfo .item {
  border: none;
  outline: none;
  border-bottom: solid 1px lightgrey;
  width: 100%;
  font-size: 1rem;
  padding-bottom: 6px;
}
.login-container .centerfix .logininfo .pwbox {
  margin-top: 26px;
}
.login-container .centerfix .logininfo .item:focus {
  border-bottom: solid 1px black;
}

.login-container .centerfix .logininfo .msg {
  /* border: solid 1px red; */
  width: 100%;
  font-size: 0.8rem;
  letter-spacing: -0.6px;
  font-weight: bold;
  color: #dc3545;
  margin-top: 12px;
}

.login-container .centerfix .logininfo .close {
  display: none;
}

.login-container .centerfix .logininfo .autologincheck {
  /* border: solid 1px red; */
  font-size: 1rem;
  width: 100%;
  margin-top: 16px;
}

.login-container .centerfix .logininfo .autologincheck > input {
  /* border: solid 1px red; */
  appearance: none;
  width: 0.9rem;
  height: 0.9rem;
  border: 1.5px solid lightgrey;
  border-radius: 0.2rem;
  margin-right: 8px;
}

.login-container .centerfix .logininfo .autologincheck > input:checked {
  border-color: transparent;
  background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e");
  background-size: 100% 100%;
  background-position: 50%;
  background-repeat: no-repeat;
  background-color: #015c56;
}
.login-container .centerfix .logininfo .loginbtn {
  width: 100%;
  height: 56px;
  font-size: 1rem;
  font-weight: bold;
  background-color: #015c56;
  color: #ffffff;
  border-radius: 30px;
  margin-top: 42px;
}

.login-container .centerfix .logininfo .loginbtn:hover {
  background-color: rgba(1, 92, 86, 0.8);
}

.login-container .centerfix .more {
  border: solid 1px #ffffff;
  font-size: 0.8rem;
}
.login-container .centerfix .more > span {
  cursor: pointer;
}
.login-container .centerfix .more > span:hover {
  color: #015c56;
  border-bottom: solid 1px #015c56;
}

.login-container .centerfix .more span:nth-child(2) {
  margin: 0 16px;
}

.hr-sect {
  width: 360px;
  height: 36px;
  display: flex;
  /* flex-basis: 100%; */
  align-items: center;
  /* color: rgba(0, 0, 0, 0.35); */
  font-size: 0.75rem;
  margin: 8px 0px;
}
.hr-sect::before,
.hr-sect::after {
  content: "";
  flex-grow: 1;
  background: rgba(0, 0, 0, 0.35);
  height: 1px;
  font-size: 0px;
  line-height: 0px;
  margin: 0px 16px;
}

.login-container .centerfix .easylogin {
  /* border: solid 1px red; */
}

Losin.js

import React from "react";
import axios from "axios";
import classnames from "classnames";
import "./Login.css";
import logoimg from "../img/yeohaengGo(logo)_crop.png";

import { 세션정보가져오기 } from "../App";
import { useNavigate } from "react-router-dom";

function Login() {
  const navigation = useNavigate();

  const [idstatus, setIdtatus] = React.useState(true);
  const [pwstatus, setPwtatus] = React.useState(true);
  const [id, setId] = React.useState("");
  const [pw, setPw] = React.useState("");
  const [emsg, setEmsg] = React.useState("");
  const [autologin, Setautologin] = React.useState(false);

  const idchange = (event) => {
    const vid = event.target.value;
    setId(vid);
    //아이디 예외처리
    if (vid === "") {
      setIdtatus(false);
    } else {
      setIdtatus(true);
    }
  };

  const pwchange = (event) => {
    const vpw = event.target.value;
    setPw(vpw);
    //비밀번호 길이 예외처리
    if (vpw === "") {
      setEmsg("비밀번호를 입력해주세요.");
      setPwtatus(false);
    } else if (pw.length > 19 || pw.length < 7) {
      setEmsg("8~20자까지 입력가능합니다.");
      setPwtatus(false);
    } else {
      setEmsg("");
      setPwtatus(true);
    }
  };

  const userlogin = async () => {
    //코드 확인
    if (!idstatus || !pwstatus) {
      return;
    }
    //자동로그인 확인
    await axios({
      url: "http://localhost:5000/login",
      method: "POST",
      data: { id: id, pw: pw, autologin: autologin },
    })
      .then((res) => {
        const { code, message } = res.data;
        if (code === "error") {
          alert(message);
          return;
        }
        // console.log("세션정보가져오기실행");
        세션정보가져오기();
        navigation("/main", { replace: true }); //{ replace: true }
      })
      .catch((e) => {
        console.log("로그인 오류!", e);
      });
  };

  //체크 확인(자동로그인)
  const handleClickRadioButton = (e) => {
    if (e.target.checked) {
      //체크
      // console.log("체크");
      Setautologin(true);
    } else {
      //안체크
      // console.log("안체크");
      Setautologin(false);
    }
  };

  return (
    <div className="login-container">
      <div className="centerfix">
        <div className="logininfo">
          <img
            src={logoimg}
            alt="logo이미지"
            onClick={() => {
              navigation("/main");
            }}
          />
          <span className="title">로그인</span>
          <input
            type="text"
            name="id"
            placeholder="아이디"
            className="item"
            onChange={idchange}
          />
          <span className={classnames("msg", { close: idstatus })}>
            아이디를 입력해주세요.
          </span>
          <input
            type="password"
            name="pw"
            placeholder="비밀번호"
            className="item pwbox"
            onChange={pwchange}
          />
          <span className={classnames("msg", { close: pwstatus })}>{emsg}</span>
          <span className="autologincheck">
            <input type="checkbox" onClick={handleClickRadioButton} />
            자동로그인
          </span>
          <button className="loginbtn" onClick={userlogin}>
            로그인
          </button>
        </div>
        <div className="more">
          <span
            onClick={() => {
              navigation("/join");
            }}
          >
            회원가입
          </span>
          <span onClick={() => {}}>아이디 찾기</span>
          <span onClick={() => {}}>비밀번호 찾기</span>
        </div>
        <div className="hr-sect">SNS 간편 로그인</div>
        <div className="easylogin">여기에 아이콘들</div>
      </div>
    </div>
  );
}

export default Login;

server.js

app.post("/login", async (req, res) => {
  /**
   * 디비에서 아이디&비번 확인
   */
  console.log(req.body);
  const id = req.body.id;
  const pw = req.body.pw;
  const autologin = req.body.autologin;

  const result = {
    code: "success",
    message: "로그인 성공",
  };

  //비밀번호 hash 암호화 MD5로
  const hashpw = md5(pw);
  console.log(hashpw);

  const queryresult = await runDB(
    `SELECT * FROM user WHERE mem_userid = '${id}' and mem_password = '${hashpw}'`
  );

  if (queryresult.length === 0) {
    result.code = "error";
    result.message = "로그인에 실패하였습니다!";
    res.send(result);
    return;
  }

  /**
   * 로그인 세션 회원정보 저장
   */
  req.session.loginUser = user[0];
  req.session.save();

  res.send(result);
});


회원가입 기능 구현

Join.css

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
  font-family: Handot;
}

u,
li {
  list-style: none;
  text-decoration: none;
}

button {
  border: 0;
  outline: 0;
  cursor: pointer;
}

.join-container {
  width: 100%;
  height: 100%;
  /* border: solid 1px red; */
}

.join-container .centerfix {
  /* border: solid 1px red; */
  width: 790px;
  height: 100%;
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.join-container .centerfix img {
  margin-top: 32px;
  margin-bottom: 48px;
  cursor: pointer;
}
.join-container .centerfix .title {
  font-size: 1.75rem;
  font-weight: bold;
  margin-bottom: 32px;
}
.join-container .centerfix .userinfo {
  /* border: solid 1px red; */
  width: 500px;
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.join-container .centerfix .userinfo div {
  /* border: solid 1px red; */
  white-space: nowrap;
}
.join-container .centerfix .userinfo div select {
  width: calc(100% / 3.2);
  padding: 0.8em 0.5em;
  border: none;
  outline: none;
  border-bottom: 1px solid lightgray;
  font-family: inherit;
  font-size: 0.875rem;
  background: url("../img/arrow.jpg") no-repeat 95% 50%;
  border-radius: 0px;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

select::-ms-expand {
  display: none;
}

.join-container .centerfix .userinfo div .full {
  width: 100%;
  margin: 0;
}
.join-container .centerfix .userinfo div .second {
  margin: 0 16px;
}

.join-container .centerfix .userinfo div .title {
  font-size: 1rem;
  font-weight: bold;
  margin-bottom: 12px;
}

.join-container .centerfix .userinfo div .title .essential {
  /* border: solid 1px red; */
  margin-left: 4px;
  font-size: 1rem;
  vertical-align: top;
  font-weight: bold;
  color: #dc3545;
}

.join-container .centerfix .userinfo div .item {
  border: none;
  outline: none;
  border-bottom: solid 1px lightgray;
  width: 100%;
  height: 40px;
  font-size: 0.875rem;
}
.join-container .centerfix .userinfo div .item:focus {
  border-bottom: solid 1px black;
}
.join-container .centerfix .userinfo div .overlap {
  width: 380px;
}

.join-container .centerfix .userinfo div .overlapbtn {
  width: 110px;
  height: 40px;
  background-color: #ffffff;
  border-radius: 7px;
  border: solid 1px black;
  margin-left: 10px;
  font-size: 0.875rem;
  text-align: center;
}

.join-container .centerfix .userinfo div .overlapbtn:hover {
  background-color: rgba(1, 92, 86, 0.1);
}

.join-container .centerfix .userinfo div .over {
  border: solid 1px #015c56;
  background-color: #015c56;
  color: #ffffff;
}
.join-container .centerfix .userinfo div .over:hover {
  background-color: rgba(1, 92, 86, 1);
  cursor: auto;
}
.join-container .centerfix .userinfo div .msgtitle {
  /* border: solid 1px red; */
  width: 100%;
  margin-top: 8px;
}
.join-container .centerfix .userinfo div .msgtitle .msg {
  /* border: solid 1px red; */
  width: 100%;
  font-size: 0.75rem;
  letter-spacing: -0.6px;
  font-weight: bold;
  color: #dc3545;
}

.join-container .centerfix .userinfo .pwcontainer > span {
  display: block;
  font-size: 0.725rem;
  font-weight: bold;
  margin-top: 8px;
  color: #888888;
}

.join-container .centerfix .userinfo .birthcontainer .line {
  /* border: solid 1px red; */
  display: flex;
}
.join-container .centerfix .userinfo .birthcontainer .line > section {
  /* border: solid 1px red; */
  width: calc(100% / 3.2);
}
.join-container
  .centerfix
  .userinfo
  .birthcontainer
  .line
  > section:nth-child(2) {
  /* border: solid 1px red; */
  margin-left: 16px;
  margin-right: 16px;
}

.join-container .centerfix .userinfo .joinbtn {
  width: 180px;
  height: 45px;
  margin: auto;
  background-color: #015c56;
  color: #ffffff;
  font-size: 1rem;
  border-radius: 7px;
  margin-bottom: 62px;
}

.join-container .centerfix .userinfo .joinbtn:hover {
  background-color: rgba(1, 92, 86, 0.8);
}

Join.js

import React, { useRef } from "react";
import axios from "axios";
import $ from "jquery";
import classnames from "classnames";
import "./Join.css";
import logoimg from "../img/yeohaengGo(logo)_crop.png";

import { useNavigate } from "react-router-dom";

function Join() {
  const navigation = useNavigate();

  const [emsg1, setEmsg1] = React.useState("");
  const [emsg2, setEmsg2] = React.useState("");
  const [emsg3, setEmsg3] = React.useState("");
  const [emsg4, setEmsg4] = React.useState("");
  const [emsg5_1, setEmsg5_1] = React.useState("");
  const [emsg5_2, setEmsg5_2] = React.useState("");
  const [emsg5_3, setEmsg5_3] = React.useState("");
  const [emsg6, setEmsg6] = React.useState("");
  const [emsg7, setEmsg7] = React.useState("");
  const [emsg8, setEmsg8] = React.useState("");
  const [emsg9, setEmsg9] = React.useState("");

  const [id, setId] = React.useState("");
  const [idisvalid, setIdisvalid] = React.useState(false);
  const [dupidisvalid, setDupidisvalid] = React.useState(false);

  const [pw, setPw] = React.useState("");
  const [rpw, setRpw] = React.useState("");
  const [samepwisvalid, setSamepwisvalid] = React.useState(false);

  const [name, setName] = React.useState("");
  const [nameisvalid, setNameisvalid] = React.useState(false);

  const [birth, setBirth] = React.useState({
    year: "",
    month: "",
    day: "",
  });

  const [email, setEmail] = React.useState("");
  const [emailisvalid, setEmailisvalid] = React.useState(false);
  const [dupemailisvalid, setDupemailisvalid] = React.useState(false);

  const [gender, setGender] = React.useState("");
  const [genderidvalid, setGenderisvalid] = React.useState(false);

  const [nickname, setNickname] = React.useState("");
  const [nicknameisvalid, setNicknameisvalid] = React.useState(false);
  const [dupnicknameisvalid, setDupnicknameisvalid] = React.useState(false);

  const [phone, setPhone] = React.useState("");
  const [phoneisvalid, setPhoneisvalid] = React.useState(true);

  const date = new Date();
  const currentyear = date.getFullYear();
  let yearArr = [];
  for (let i = currentyear; i > currentyear - 100; i--) {
    yearArr.push(i);
  }
  const monthArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
  const dayArr = [
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
    22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
  ];

  const inputRef = useRef([]);

  const idvaluechange = (event) => {
    const idRegex = /^[A-Za-z0-9]{4,12}$/;
    const vid = event.target.value;
    setId(vid);
    setDupidisvalid(false);
    //아이디 예외처리
    if (vid === "") {
      setEmsg1("필수입니다.");
      setIdisvalid(false);
    } else {
      if (!idRegex.test(vid)) {
        setEmsg1("영어+숫자 4~12 글자로 입력해주세요.");
        setIdisvalid(false);
      } else {
        setEmsg1("");
        setIdisvalid(true);
      }
    }
  };

  const pwvaluechange = (event) => {
    const passwordRegex =
      /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,20}$/;
    const vpw = event.target.value;
    setPw(vpw);
    //비밀번호 예외처리
    if (vpw === "") {
      setEmsg2("필수입니다.");
      setSamepwisvalid(false);
    } else {
      setEmsg2("");
      if (vpw === rpw) {
        setEmsg3("비밀번호가 일치합니다.");
        setSamepwisvalid(true);
      } else {
        setEmsg3("비밀번호가 일치하지 않습니다.");
        setSamepwisvalid(false);
      }
      // if (!passwordRegex.test(vpw)) {
      //   setEmsg2("숫자+영문자+특수문자 조합으로 8~20자리 입력해주세요.");
      //   setPwisvalid(false);
      // } else {
      //   setEmsg2("");
      // }
    }
  };

  const repwvaluechange = (event) => {
    const vrpw = event.target.value;
    setRpw(vrpw);
    //비밀번호확인 예외처리
    if (vrpw === "") {
      setEmsg3("필수입니다.");
      setSamepwisvalid(false);
    } else {
      if (vrpw === pw) {
        setEmsg3("비밀번호가 일치합니다.");
        setSamepwisvalid(true);
      } else {
        setEmsg3("비밀번호가 일치하지 않습니다.");
        setSamepwisvalid(false);
      }
    }
  };

  const namevaluechange = (event) => {
    const vname = event.target.value;
    setName(vname);
    //이름 예외처리
    if (vname === "") {
      setEmsg4("필수입니다.");
      setNameisvalid(false);
    } else {
      if (vname.length < 2 || vname.length > 5) {
        setEmsg4("2글자 이상 5글자 미만으로 입력해주세요.");
        setNameisvalid(false);
      } else {
        setEmsg4("");
        setNameisvalid(true);
      }
    }
  };

  const birthvaluechange = (event) => {
    const target = $(event.target);
    const name = target.closest("select").attr("id");
    const vbrith = event.target.value;

    const cloneData = { ...birth };
    cloneData[name] = vbrith;
    setBirth(cloneData);

    // console.log(cloneData);

    if (name === "year" && vbrith === "") {
      setEmsg5_1("필수입니다.");
    } else if (name === "month" && vbrith === "") {
      setEmsg5_2("필수입니다.");
    } else if (name === "day" && vbrith === "") {
      setEmsg5_3("필수입니다.");
    } else if (name === "year" && vbrith) {
      setEmsg5_1("");
    } else if (name === "month" && vbrith) {
      setEmsg5_2("");
    } else if (name === "day" && vbrith) {
      setEmsg5_3("");
    }
  };

  const emailvaluechange = (event) => {
    const emailRegex =
      /([\w-.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
    const vemail = event.target.value;
    setEmail(vemail);
    setDupemailisvalid(false);
    //email 예외처리
    if (vemail === "") {
      setEmsg6("필수입니다.");
      setEmailisvalid(false);
    } else {
      if (!emailRegex.test(vemail)) {
        setEmsg6("이메일 형식이 아닙니다.");
        setEmailisvalid(false);
      } else {
        setEmsg6("");
        setEmailisvalid(true);
      }
    }
  };

  const gendervaluechange = (event) => {
    const vgender = event.target.value;
    // console.log(vgender);
    setGender(vgender);
    if (vgender === "") {
      setEmsg7("필수입니다.");
      setGenderisvalid(false);
    } else {
      setEmsg7("");
      setGenderisvalid(true);
    }
  };

  const nicknamevaluechange = (event) => {
    const vnname = event.target.value;
    setNickname(vnname);
    setDupnicknameisvalid(false);
    //이름 예외처리
    if (vnname === "") {
      setEmsg8("필수입니다.");
      setNicknameisvalid(false);
    } else {
      if (vnname.length > 12) {
        setEmsg8("12글자 미만으로 입력해주세요.");
        setNicknameisvalid(false);
      } else {
        setEmsg8("");
        setNicknameisvalid(true);
      }
    }
  };

  const phonevaluechange = (event) => {
    const phoneRegex = /^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$/;
    const vphone = event.target.value;

    setPhone(vphone);

    if (vphone === "") {
      setEmsg9("");
      setPhoneisvalid(true);
      return;
    }
    if (!phoneRegex.test(vphone)) {
      setEmsg9("잘못된 번호입니다. 다시 확인해주세요.");
      setPhoneisvalid(false);
    } else {
      setEmsg9("");
      setPhoneisvalid(true);
    }
  };

  /**
   * 아이디 중복검사
   */
  const duplicateIdCheck = async () => {
    if (!idisvalid) {
      // console.log("아이디가 유효하지 않습니다");
      return;
    }
    if (dupidisvalid) {
      // console.log("아이디 중복 확인 완료");
      return;
    }
    await axios({
      url: "http://localhost:5000/idcheck",
      method: "POST",
      data: { id: id },
    })
      .then((res) => {
        const { code, message } = res.data;
        if (code === "error") {
          setEmsg1(message);
          setIdisvalid(false);
          setDupidisvalid(false);
        } else {
          setEmsg1(message);
          setIdisvalid(true);
          setDupidisvalid(true);
        }
      })
      .catch((e) => {
        console.log("아이디 중복체크 오류!", e);
      });
  };

  /**
   * 이메일 중복검사
   */
  const duplicateEmailCheck = async () => {
    if (!emailisvalid) {
      // console.log("이메일이 유효하지 않습니다");
      return;
    }
    if (dupemailisvalid) {
      // console.log("이메일 중복 확인 완료");
      return;
    }
    await axios({
      url: "http://localhost:5000/emailcheck",
      method: "POST",
      data: { email: email },
    })
      .then((res) => {
        const { code, message } = res.data;
        if (code === "error") {
          setEmsg6(message);
          setEmailisvalid(false);
          setDupemailisvalid(false);
        } else {
          setEmsg6(message);
          setEmailisvalid(true);
          setDupemailisvalid(true);
        }
      })
      .catch((e) => {
        console.log("이메일 중복체크 오류!", e);
      });
  };

  /**
   * 닉네임 중복검사
   */
  const duplicatenickCheck = async () => {
    if (!nicknameisvalid) {
      // console.log("닉네임이 유효하지 않습니다");
      return;
    }
    if (dupnicknameisvalid) {
      // console.log("닉네임 중복 확인 완료");
      return;
    }
    await axios({
      url: "http://localhost:5000/nicknamecheck",
      method: "POST",
      data: { nickname: nickname },
    })
      .then((res) => {
        const { code, message } = res.data;
        if (code === "error") {
          setEmsg8(message);
          setNicknameisvalid(false);
          setDupnicknameisvalid(false);
        } else {
          setEmsg8(message);
          setNicknameisvalid(true);
          setDupnicknameisvalid(true);
        }
      })
      .catch((e) => {
        console.log("닉네임 중복체크 오류!", e);
      });
  };
  /**
   * 회원가입 값 유효성 검사
   */
  const joinisvalid = () => {
    if (!idisvalid) {
      console.log("아이디를 확인하세요");
      inputRef.current[0].focus();
      return;
    }
    if (!dupidisvalid) {
      console.log("아이디 중복을 확인하세요");
      return;
    }
    if (!samepwisvalid) {
      console.log("비밀번호를 확인하세요");
      inputRef.current[1].focus();
      return;
    }
    if (!nameisvalid) {
      console.log("이름을 확인하세요");
      inputRef.current[2].focus();
      return;
    }
    //생일
    if (!birth.year || !birth.month || !birth.day) {
      console.log("날짜를 확인하세요");
      return;
    }
    if (!emailisvalid) {
      console.log("이메일을 확인하세요");
      inputRef.current[3].focus();
      return;
    }
    if (!dupemailisvalid) {
      console.log("이메일 중복을 확인하세요");
      return;
    }
    if (!genderidvalid) {
      console.log("성별을 확인하세요");
      return;
    }
    if (!nicknameisvalid) {
      console.log("닉네임을 확인하세요");

      inputRef.current[4].focus();
      return;
    }
    if (!dupnicknameisvalid) {
      console.log("닉네임 중복을 확인하세요");
      return;
    }
    if (!phoneisvalid) {
      console.log("번호를 확인하세요");
      return;
    }

    // console.log({
    //   id: id,
    //   pw: pw,
    //   name: name,
    //   birth: birth,
    //   email: email,
    //   gender: gender,
    //   nickname: nickname,
    //   phone: phone,
    // });

    joincheck();
  };

  const joincheck = async () => {
    await axios({
      url: "http://localhost:5000/join",
      method: "POST",
      data: {
        id: id,
        pw: pw,
        name: name,
        birth: birth,
        email: email,
        gender: gender,
        nickname: nickname,
        phone: phone,
      },
    })
      .then((res) => {
        const { code, message } = res.data;
        if (code === "error") {
          alert(message);
          return;
        }
        navigation("/login");
      })
      .catch((e) => {
        console.log("회원가입 오류!", e);
      });
  };

  return (
    <div className="join-container">
      {/* <div className="toast">어쩌고노ㅓ라어ㅣ나어리</div>; */}
      <div className="centerfix">
        <img
          src={logoimg}
          alt="logo이미지"
          onClick={() => {
            navigation("/main");
          }}
        />
        <span className="title">회원가입</span>
        <div className="userinfo">
          <div className="idcontainer">
            <section className="title">
              <span>아이디</span>
              <span className="essential">*</span>
            </section>
            <input
              ref={(el) => (inputRef.current[0] = el)}
              type="text"
              name="id"
              placeholder="아이디"
              className="item overlap"
              onChange={idvaluechange}
            />
            <button
              className={classnames("overlapbtn", { over: dupidisvalid })}
              onClick={duplicateIdCheck}
            >
              중복확인
            </button>
            <section className="msgtitle">
              <span className="msg">{emsg1}</span>
            </section>
          </div>
          <div className="pwcontainer">
            <section className="title">
              <span>비밀번호</span>
              <span className="essential">*</span>
            </section>
            <input
              ref={(el) => (inputRef.current[1] = el)}
              type="password"
              name="pw"
              placeholder="비밀번호"
              className="item"
              onChange={pwvaluechange}
            />
            <span>
              8~20자까지 영문,숫자,특수문자(_!@#$%^&*)모두 조합하여 입력
            </span>
            <section className="msgtitle">
              <span className="msg">{emsg2}</span>
            </section>
          </div>
          <div className="repwcontainer">
            <section className="title">
              <span>비밀번호 재입력</span>
              <span className="essential">*</span>
            </section>
            <input
              type="password"
              name="repw"
              placeholder="비밀번호 재입력"
              className="item"
              onChange={repwvaluechange}
            />
            <section className="msgtitle">
              <span className="msg">{emsg3}</span>
            </section>
          </div>
          <div className="namecontainer">
            <section className="title">
              <span>이름</span>
              <span className="essential">*</span>
            </section>
            <input
              ref={(el) => (inputRef.current[2] = el)}
              type="text"
              name="name"
              placeholder="이름"
              className="item"
              onChange={namevaluechange}
            />
            <section className="msgtitle">
              <span className="msg">{emsg4}</span>
            </section>
          </div>
          <div className="birthcontainer">
            <section className="title">
              <span>생년월일</span>
              <span className="essential">*</span>
            </section>
            <select
              className="b-box"
              id="year"
              required
              onChange={birthvaluechange}
            >
              <option value="">연도</option>
              {yearArr.map((year, index) => (
                <option key={index} value={year}>
                  {year}
                </option>
              ))}
            </select>
            <select
              className="b-box second"
              id="month"
              required
              onChange={birthvaluechange}
            >
              <option value=""></option>
              {monthArr.map((month, index) => (
                <option key={index} value={month}>
                  {month}
                </option>
              ))}
            </select>
            <select
              className="b-box"
              id="day"
              required
              onChange={birthvaluechange}
            >
              <option value=""></option>
              {dayArr.map((day, index) => (
                <option key={index} value={day}>
                  {day}
                </option>
              ))}
            </select>
            <div className="line">
              <section className="msgtitle">
                <span className="msg">{emsg5_1}</span>
              </section>
              <section className="msgtitle">
                <span className="msg">{emsg5_2}</span>
              </section>
              <section className="msgtitle">
                <span className="msg">{emsg5_3}</span>
              </section>
            </div>
          </div>
          <div className="emailcontainer">
            <section className="title">
              <span>이메일</span>
              <span className="essential">*</span>
            </section>
            <input
              ref={(el) => (inputRef.current[3] = el)}
              type="text"
              name="email"
              placeholder="geenee@gmail.com"
              className="item overlap"
              onChange={emailvaluechange}
            />
            <button
              className={classnames("overlapbtn", { over: dupemailisvalid })}
              onClick={duplicateEmailCheck}
            >
              중복확인
            </button>
            <section className="msgtitle">
              <span className="msg">{emsg6}</span>
            </section>
          </div>
          <div className="gendercontainer">
            <section className="title">
              <span>성별</span>
              <span className="essential">*</span>
            </section>
            <select
              className="g-box full"
              id="gender-list"
              required
              onChange={gendervaluechange}
            >
              <option value="">성별</option>
              <option value="M">남성</option>
              <option value="F">여성</option>
            </select>
            <section className="msgtitle">
              <span className="msg">{emsg7}</span>
            </section>
          </div>
          <div className="nicknamecontainer">
            <section className="title">
              <span>닉네임</span>
              <span className="essential">*</span>
            </section>
            <input
              ref={(el) => (inputRef.current[4] = el)}
              type="text"
              name="nickname"
              placeholder="닉네임"
              className="item overlap"
              onChange={nicknamevaluechange}
            />
            <button
              className={classnames("overlapbtn", { over: dupnicknameisvalid })}
              onClick={duplicatenickCheck}
            >
              중복확인
            </button>
            <section className="msgtitle">
              <span className="msg">{emsg8}</span>
            </section>
          </div>
          <div className="phonecontainer">
            <section className="title">
              <span>휴대폰 번호</span>
            </section>
            <input
              type="text"
              name="phone"
              placeholder="010-1234-1234"
              className="item"
              onChange={phonevaluechange}
            />
            <section className="msgtitle">
              <span className="msg">{emsg9}</span>
            </section>
          </div>
          <button className="joinbtn" onClick={joinisvalid}>
            회원가입
          </button>
        </div>
      </div>
    </div>
  );
}

export default Join;

server.js

const express = require("express");
const cors = require("cors");
const session = require("express-session");
const mysql = require("mysql2");
const md5 = require("md5");

const db = mysql.createPoolCluster();
const app = express();
const port = 5000;

app.use(express.json());

app.use(
  session({
    secret: "SECRET",
    resave: false,
    saveUninitialized: true,
  })
);

app.use(
  cors({
    origin: true,
    credentials: true,
  })
);

db.add("project", {
  host: "127.0.0.1",
  user: "root",
  password: "",
  database: "frontend_project",
  port: 3306,
});

function runDB(query) {
  return new Promise(function (resolve, reject) {
    db.getConnection("project", function (error, connection) {
      if (error) {
        console.log("DB Connection Error!", error);
        reject(true);
      }

      connection.query(query, function (error, data) {
        if (error) {
          console.log("query error", error);
          reject(true);
        }

        resolve(data);
      });
      connection.release();
    });
  });
}

app.get("/", (req, res) => {
  res.send("여기로 옵니다!");
});

app.post("/idcheck", async (req, res) => {
  /**
   * 아이디 중복체크
   */
  // console.log(req.body);
  const id = req.body.id;

  const result = {
    code: "success",
    message: "사용 가능한 아이디입니다.",
  };

  const queryresult = await runDB(
    `SELECT * FROM user WHERE mem_userid = '${id}'`
  );

  if (queryresult.length > 0) {
    result.code = "error";
    result.message = "이미 사용중인 아이디입니다.";
    res.send(result);
    return;
  }

  res.send(result);
});

app.post("/emailcheck", async (req, res) => {
  /**
   * 이메일 중복체크
   */
  // console.log(req.body);
  const email = req.body.email;

  const result = {
    code: "success",
    message: "사용 가능한 이메일입니다.",
  };

  const queryresult = await runDB(
    `SELECT * FROM user WHERE mem_email = '${email}'`
  );

  if (queryresult.length > 0) {
    result.code = "error";
    result.message = "이미 사용중인 이메일입니다.";
    res.send(result);
    return;
  }

  res.send(result);
});

app.post("/nicknamecheck", async (req, res) => {
  /**
   * 닉네임 중복체크
   */
  console.log(req.body);
  const nickname = req.body.nickname;

  const result = {
    code: "success",
    message: "사용 가능한 닉네임입니다.",
  };

  const queryresult = await runDB(
    `SELECT * FROM user WHERE mem_nickname = '${nickname}'`
  );

  if (queryresult.length > 0) {
    result.code = "error";
    result.message = "이미 사용중인 닉네임입니다.";
    res.send(result);
    return;
  }

  res.send(result);
});

app.post("/join", async (req, res) => {
  /**
   *
   * DB에 id,nickname,pw insert
   */
  const { id, pw, name, birth, email, gender, nickname, phone } = req.body;

  const hashpw = md5(pw);
  const month = birth.month.length === 1 ? "0" + birth.month : birth.month;
  const day = birth.day.length === 1 ? "0" + birth.day : birth.day;
  const bdate = `${birth.year}-${month}-${day}`;

  const result = {
    code: "success",
    message: "회원가입이 완료되었습니다!",
  };

  let queryresult = "";
  phone === ""
    ? (queryresult = `INSERT INTO USER(mem_userid,mem_password,mem_username,mem_birth,mem_email,mem_gender,mem_nickname,mem_phone,mem_regtime) VALUES ('${id}','${hashpw}','${name}','${bdate}','${email}','${gender}','${nickname}',NULL,NOW())`)
    : (queryresult = `INSERT INTO USER(mem_userid,mem_password,mem_username,mem_birth,mem_email,mem_gender,mem_nickname,mem_phone,mem_regtime) VALUES ('${id}','${hashpw}','${name}','${bdate}','${email}','${gender}','${nickname}','${phone}',NOW())`);
  await runDB(queryresult);

  res.send(result);
});

app.post("/login", async (req, res) => {
  /**
   * 디비에서 아이디&비번 확인
   */
  console.log(req.body);
  const id = req.body.id;
  const pw = req.body.pw;
  const autologin = req.body.autologin;

  const result = {
    code: "success",
    message: "로그인 성공",
  };

  //비밀번호 hash 암호화 MD5로
  const hashpw = md5(pw);
  console.log(hashpw);

  const queryresult = await runDB(
    `SELECT * FROM user WHERE mem_userid = '${id}' and mem_password = '${hashpw}'`
  );

  if (queryresult.length === 0) {
    result.code = "error";
    result.message = "로그인에 실패하였습니다!";
    res.send(result);
    return;
  }

  /**
   * 로그인 세션 회원정보 저장
   */
  req.session.loginUser = queryresult[0];
  req.session.save();

  res.send(result);
});

app.get("/user", (req, res) => {
  console.log(req.session.loginUser);
  res.send(req.session.loginUser);
});

app.get("/userdelete", (req, res) => {
  req.session.loginUser = {};
  req.session.save();
  res.send(req.session.loginUser);
});

app.listen(port, () => {
  console.log("서버가 시작되었습니다");
});

server.js도 기능에 따라 코드 파일 분리 해야할듯~
npm install sweetalert -> alert 테마 사용 가능
sweetalert2 USAGE
내가 원하는 기능은 Toast?이거인듯 일단 alert는 안넣음
useRef 이용해서 회원가입 버튼 눌렀을 때 수정해야하는 값이 있으면 해당 input 태그로 focus되는 기능 추가



로그인 세션에 들어가있는 데이터

3번 데이터가 페이지로 회원가입해서 들어간 데이터임

소스는 깃헙에 올리겠음,,

profile
코딩 공부 기록용

0개의 댓글