project/모임웹프로젝트

모임웹프로젝트 회원가입페이지 프런트 구성1 (3)

naspeciallist 2024. 9. 28. 18:23

저번 게시물에서 설정하였던 user엔티티를 이용하여 회원가입 기능을 구현하겠습니다.
먼저 회원가입 페이지에 대한 화면 설계입니다.

회원가입 페이지

 

위 화면 설계와 같은 방식을 이용하여 회원가입 페이지를 구성해 보도록 하겠습니다.

 

먼저 html content 부분입니다.

<h1>계정 만들기</h1>
<form action="/signup" method="POST" enctype="multipart/form-data">

  <div class="form-group">
    <label for="uid">아이디</label>
    <input type="text" id="uid" name="uid" required>
    <button type="button" class="check-btn" onclick="checkUsername()">중복 확인</button>
  </div>

  <div class="form-group">
    <label for="password">비밀번호</label>
    <input type="password" id="password" name="password" required>

  </div>

  <div class="form-group">
    <label for="name">이름</label>
    <input type="text" id="name" name="name" required>
  </div>

  <div class="form-group">
    <label for="email">이메일</label>
    <input type="email" id="email" name="email" required>
    <button type="button" class="check-btn" onclick="verifyEmail()">본인 인증</button>
  </div>

  <div class="form-group">
    <label for="birthdate">생년월일</label>
    <input type="date" id="birthdate" name="birthdate" required>
  </div>

  <div class="form-group">
    <label for="introduce">자기소개</label>
    <input type="text" id="introduce" name="introduce" placeholder="한줄로 자기소개를 해주세요" required>
  </div>

  <div class="form-group" style="width: 635px">
    <label style="width: 300px; float: left;">성별(성별을 선택해주세요)</label>
    <div class="gender-buttons form-group">
      <input type="radio" id="male" name="gender" value="남성" required>
      <label for="male">남성</label>

      <input type="radio" id="female" name="gender" value="여성" required>
      <label for="female">여성</label>
    </div>
  </div>

  <div style="width: 635px; margin-bottom: 30px">
    <label>프로필 사진 추가 (최소 3장, 최대 6장)</label>
  </div>
  <div id="photo-section" class="profile-photo-section" style="margin-bottom:70px">
    <div class="photo-box" onclick="document.getElementById('photoInput1').click()">
      <img id="preview1" src="#" alt="프로필 사진" style="display:none;">
      <div class="add-photo-button">+</div>
      <input type="file" id="photoInput1" class="hidden-input" accept="image/*" onchange="previewImage(event, 1)">
    </div>
  </div>

  <!-- 관심사 추가 및 재설정 버튼 섹션 -->
  <div style="width: 635px; margin-bottom: 30px">
    <label>관심사 추가</label>
  </div>
  <div class="interest-section">
    <div id="interestButtonsContainer">
      <!-- 관심사 선택 후 버튼들이 동적으로 추가됩니다. -->
      <button type="button" id="addInterestBtn" onclick="openModal()">+ 관심사 추가</button>
    </div>
    <button type="button" id="resetInterestBtn" onclick="resetInterests()">관심사 재설정</button>
  </div>

  <div id="selectedInterests" class="selected-interests">
    <!-- 선택된 관심사 버튼이 이곳에 추가됩니다. -->
  </div>


  <button type="submit" class="submit-btn">회원가입하기</button>
</form>

<footer>
  <a href="#">회사소개</a> | <a href="#">이용약관</a> | <a href="#">개인정보처리방침</a>
</footer>

<!-- 관심사 모달 -->
<div id="interestModal" class="modal">
  <div class="modal-content">
    <span class="close" onclick="closeModal()">&times;</span>
    <div class="modal-header">관심사 선택 (최대 8개)</div>
    <div class="modal-body">
      <div class="interest-list">
        <button class="interest-button" data-interest="운동" onclick="toggleInterestSelection(this)">운동</button>
        <button class="interest-button" data-interest="음악" onclick="toggleInterestSelection(this)">음악</button>
        <button class="interest-button" data-interest="독서" onclick="toggleInterestSelection(this)">독서</button>
        <button class="interest-button" data-interest="여행" onclick="toggleInterestSelection(this)">여행</button>
        <button class="interest-button" data-interest="영화" onclick="toggleInterestSelection(this)">영화</button>
        <button class="interest-button" data-interest="사진" onclick="toggleInterestSelection(this)">사진</button>
        <button class="interest-button" data-interest="게임" onclick="toggleInterestSelection(this)">게임</button>
        <button class="interest-button" data-interest="등산" onclick="toggleInterestSelection(this)">등산</button>
        <button class="interest-button" data-interest="명상" onclick="toggleInterestSelection(this)">명상</button>
        <button class="interest-button" data-interest="패션" onclick="toggleInterestSelection(this)">패션</button>
        <button class="interest-button" data-interest="캠핑" onclick="toggleInterestSelection(this)">캠핑</button>
        <button class="interest-button" data-interest="쇼핑" onclick="toggleInterestSelection(this)">쇼핑</button>
        <button class="interest-button" data-interest="디자인" onclick="toggleInterestSelection(this)">디자인</button>
        <button class="interest-button" data-interest="DIY" onclick="toggleInterestSelection(this)">DIY</button>
        <button class="interest-button" data-interest="과학" onclick="toggleInterestSelection(this)">과학</button>
        <button class="interest-button" data-interest="기술" onclick="toggleInterestSelection(this)">기술</button>
        <button class="interest-button" data-interest="요가" onclick="toggleInterestSelection(this)">요가</button>
        <button class="interest-button" data-interest="춤" onclick="toggleInterestSelection(this)">춤</button>
        <button class="interest-button" data-interest="자동차" onclick="toggleInterestSelection(this)">자동차</button>
        <button class="interest-button" data-interest="재테크" onclick="toggleInterestSelection(this)">재테크</button>
      </div>
      <input type="text" id="customInterest" placeholder="직접 입력" class="custom-input" />
      <button class="interest-button" onclick="selectCustomInterest()">직접 입력</button>
    </div>
    <div class="modal-footer">
      <button class="modal-save-button" onclick="saveSelectedInterests()">선택 완료</button>
    </div>
  </div>
</div>

 

각 폼에서 값을 받을 수 있게 구성을 하였고 관심사는 모달창을 통해서 받을 수 있도록 구성하였습니다.

 

css파일입니다.

body {
  font-family: Arial, sans-serif;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  margin: 0;
}
h1 {
  margin-bottom: 100px;
  text-align: center;
}
form {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.form-group {
  display: flex;
  align-items: center;
  margin-bottom: 60px;
}
label {
  width: 100px;
  font-size: 20px;
  font-weight: bold;
}
input[type="text"],input[type="email"] ,input[type="date"], input[type="password"] {
  padding: 10px;
  width: 500px;
  border: 1px solid black;
  border-radius: 15px;
}

#uid{
  width: 355px;
}

input[type="email"]{
  width: 355px
}

.gender-buttons, .interest-section {
  display: flex;
  gap: 20px;
  margin: 15px 0;
  justify-content: center;
}
.gender-buttons label, .interest-section button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 150px;
  height: 45px;
  border: 2px solid #000000;
  border-radius: 15px;
  cursor: pointer;
  text-align: center;
  font-size: 20px;
  background-color: white;
}
.gender-buttons input[type="radio"] {
  display: none; /* 라디오 버튼 숨기기 */
}
.gender-buttons input[type="radio"]:checked + label {
  background-color: #ff6b6b;
  color: white;
  border-color: #ff6b6b;
}
.profile-photo-section {
  display: flex;
  justify-content: center;
  gap: 20px; /* 사진 상자 사이 간격 추가 */
  flex-wrap: wrap; /* 사진이 많아질 경우 다음 줄로 넘어가게 설정 */
}

.photo-box {
  width: 150px;  /* 사진 상자 너비 확대 */
  height: 200px; /* 사진 상자 높이 확대 */
  border: 2px dashed #ccc;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
  overflow: hidden;
  margin-bottom: 20px; /* 사진 상자 아래 간격 추가 */
}

.photo-box img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.add-photo-button {
  position: absolute;
  bottom: 10px;
  right: 10px;
  background-color: #ff6b6b;
  border: none;
  border-radius: 50%;
  width: 30px;  /* 추가 버튼 크기 확대 */
  height: 30px; /* 추가 버튼 크기 확대 */
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  font-size: 18px;
  line-height: 0;
}

.submit-btn {
  margin-top: 150px;
  padding: 15px 30px;
  border: 1px solid black;
  background-color: white;
  border-radius: 15px;
  cursor: pointer;
  font-size: 20px;
  text-align: center;
  width: 300px;
  height: auto;
  color: black;
}
footer {
  margin-top: 80px;
  font-size: 12px;
  text-align: center;
}
footer a {
  text-decoration: none;
  color: #333;
  margin: 0 10px;
}
.hidden-input {
  display: none;
}
/* 선택된 관심사 섹션 스타일 */
.selected-interests {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 20px;
}

.selected-interest-button {
  padding: 10px 15px;
  background-color: #e0f7fa;
  border: 1px solid #00acc1;
  border-radius: 5px;
  cursor: pointer;
}

.selected-interest-button:hover {
  background-color: #b2ebf2;
}

/* 모달 스타일 */
.modal {
  display: none;
  position: fixed;
  z-index: 1;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(5px);
}

.modal-content {
  background-color: #fff;
  margin: 10% auto;
  padding: 20px;
  border-radius: 15px;
  width: 80%;
  max-width: 500px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.modal-header {
  font-size: 24px;
  font-weight: bold;
  margin-bottom: 15px;
  text-align: center;
  color: #333;
}

.modal-body {
  max-height: 300px;
  overflow-y: auto;
}

.modal-footer {
  text-align: center;
  margin-top: 10px;
}

.modal-save-button {
  padding: 10px 20px;
  background-color: #2aaff8;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

.interest-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
}

.interest-button {
  padding: 15px 30px; /* 버튼 크기를 키움 */
  background-color: #f7f7f7;
  border: 1px solid #ddd;
  border-radius: 10px;
  cursor: pointer;
  transition: all 0.3s ease;
  font-size: 16px; /* 글씨 크기를 키움 */
  font-weight: bold;
}

.interest-button.selected {
  background-color: #ffffff;
  border-color: #2aaff8;
}

.interest-button:hover {
  background-color: #e0e0e0;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  transform: translateY(-2px);
}

.close {
  color: #888;
  float: right;
  font-size: 28px;
  font-weight: bold;
  cursor: pointer;
}

/* 입력창 스타일 수정 */
.custom-input {
  padding: 5px;
  width: 150px; /* 더 작은 크기 */
  border: 1px solid #ccc;
  border-radius: 10px;
  font-size: 12px; /* 더 작은 글씨 */
}

.custom-input:focus {
  outline: none;
  border-color: #007bff;
  box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
}

.check-btn {
  margin-left: 10px;
  padding: 8px;
  background-color: #fbfbfb;
  color: #000000;
  border: 1px solid black;
  border-radius: 15px;
  cursor: pointer;
  width: 135px;
  /* height: 37px; */
}

.check-btn:disabled {
  background-color: #ccc;
}

 

스크립트를 통해서 유효성 검사도 추가를 하였습니다. 유효성검사는 테스트를 용이하게 하기위해 나중에 완성 후 테스트 단계 때 sumit에 연결하도록 하겠습니다.

 

function validateForm() {
  // 아이디: 5자 이상, 20자 이하, 영문자와 숫자만 허용
  const uid = document.getElementById('uid').value;
  const uidPattern = /^[a-zA-Z0-9]{5,20}$/;
  if (!uidPattern.test(uid)) {
    alert('아이디는 5~20자의 영문자와 숫자만 허용됩니다.');
    return false;
  }

  // 비밀번호: 8자 이상, 영문자, 숫자, 특수문자 조합
  const password = document.getElementById('password').value;
  const passwordPattern = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$/;
  if (!passwordPattern.test(password)) {
    alert('비밀번호는 최소 8자, 영문자, 숫자, 특수문자를 포함해야 합니다.');
    return false;
  }

  // 이메일 포맷 검사
  const email = document.getElementById('email').value;
  const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  if (!emailPattern.test(email)) {
    alert('올바른 이메일 형식을 입력해주세요.');
    return false;
  }

  // 생년월일 검사
  const birthdate = document.getElementById('birthdate').value;
  const birthYear = new Date(birthdate).getFullYear();
  const currentYear = new Date().getFullYear();
  if (currentYear - birthYear < 18) {
    alert('만 18세 이상만 가입할 수 있습니다.');
    return false;
  }

  return true;
}

 

id를 통해 html로 부터 값을 가져오고 패턴매칭과 조건문을 통해 검사 후 조건에 부합하지 않으면 폼이 제출되는 걸 방지한다.