회원정보 수정에는 내정보 수정, 패스원드 변경, 회원탈퇴가 있다. 이번 포스트에서는 내 정보 수정을 다뤄보겠다.
처음에는 아래와 같이 My Page에서 탭을 나누어 만들었다. 하지만 만들고 나서 직접 사용해 보니 다른탭으로 이동할 때 카테고리(?) 같은 탭이 없어서 불편하였다.
카테고리 형식으로 아래와 같이 수정하여 View를 구성하였다.
이번 포스트에서는 회원관리 정보를 위주로 다루기 때문에 게시물 관련 정보는 추후에 업로드 하도록 하겠다.
사이드바는 아래와 같이 구성하여 Thymeleaf의 fragments를 이용하여 해당 page에 include시켰다
<html xmlns:th="http://www.thymeleaf.org">
<head>
...
</head>
<body>
<div th:fragment="mySideBar">
<div id="sidebar-wrapper">
<ul class="sidebar-nav">
<li class="sidebar-brand">
<a>마이페이지</a>
</li>
<li>
<a th:href="@{/board/myBoard}">내 게시물</a>
</li>
<li>
<a th:href="@{/board/myOldBoard}">오래된 게시물</a>
</li>
<li>
<a th:href="@{/account/myinfo}">내 정보 수정</a>
</li>
<li>
<a th:href="@{/account/password}">패스워드 변경</a>
</li>
<li>
<a th:href="@{/account/withdrawal}">회원 탈퇴</a>
</li>
</ul>
</div>
</div>
<script> ... </script>
</body>
</html>
내 정보 수정 view는 아래와 같이 구현하였다. form형식으로 DB에 저장되어있는 해당 회원의 정보를 thymeleaf의 Object와 field를 이용하여서 받아왔다. 당시 이 페이지를 구현할 때 Thymeleaf 사용이 처음이어서 다양한 방법을 적용해 보자 해서 Object로 구현했다.
회원은 닉네임과 생년월일을 수정할 수 있고, 닉네임을 수정 시 JS의 ajax를 이용해 비동기 방식으로 중복 체크를 진행 하였다. 뿐만 아니라 생년월일을 수정하면 자동으로 나이가 계산되도록 JS를 작성하였다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>...</head>
<body>
...
<div th:include="fragments/mypagesidebar"> </div> // 사이드바 include
<form class="form-signin" th:action="@{/account/myinfo}" method="post" th:object="${member}">
<h1 class="h3 mb-3 fw-normal">내 정보 수정</h1>
<hr>
<input type="hidden" class="form-control" th:field="*{id}" th:readonly="readonly" >
<input type="hidden" class="form-control" th:field="*{role}" th:readonly="readonly" >
<div class="mb-3">
<label th:for="username">이메일</label>
<input type="email" class="form-control" th:field="*{username}" th:readonly="readonly" >
</div>
<div class="mb-3">
<label th:for="nickname">닉네임</label>
<input type="text" class="form-control" th:field="*{nickname}" oninput="nicknameModify()">
<span class="nickname_ok">사용 가능한 이름입니다.</span>
<span class="nickname_already">누군가 이 이름을 사용하고 있어요.</span>
</div>
<label class="mb-3 form-label" for="birth">생년월일</label>
<div class="mb-3" id="birth" onchange="checkAgeModify()">
<div class="birth dropdown">
<select class="form-control" id="year" name="year">
<option th:value="*{year}" th:text="*{year}" selected></option>
<option value="">년(YY)</option>
<th:block th:each="a: ${#numbers.sequence(2001,1900)}">
<option th:value="${a}" th:text="${a}"></option>
</th:block>
</select>
</div>
<div class="birth mx-1 dropdown">
<select class="form-control" id="month" name="month">
<option th:value="*{month}" th:text="*{month}" selected></option>
<option value="">월(mm)</option>
<th:block th:each="month: ${#numbers.listFormatInteger(#numbers.sequence(1,12),2)}">
<option class="dropdown-item" th:value="${month}" th:text="${month}"></option>
</th:block>
</select>
</div>
<div class="birth dropdown">
<select class="form-control" id="day" name="day">
<option th:value="*{day}" th:text="*{day}" selected></option>
<option value="">일(dd)</option>
<th:block th:each="day: ${#numbers.listFormatInteger(#numbers.sequence(1,31),2)}">
<option class="dropdown-item" th:value="${day}" th:text="${day}"></option>
</th:block>
</select>
</div>
<span id="birth_check_msg"></span>
</div>
<div class="mb-3">
<label for="age">나이</label>
<input type="text" class="form-control" id="age" th:readonly="readonly" >
</div>
<div class="mb-3">
<label th:for="gender">성별</label>
<input type="text" class="form-control" id="gender" name="gender" th:value="*{gender.value}" hidden >
<input type="text" class="form-control" th:value="*{gender.title}" readonly>
</div>
<div class="mb-3">
<button class="w-100 btn btn-lg btn-primary" type="submit" onclick="return modifyCheckAll()">수정</button>
</div>
<div class="mb-3">
<a type="button" style="color:white" class="w-100 btn btn-lg btn-secondary" onclick="window.history.back();">취소</a>
</div>
</form>
<footer th:replace="fragments/common :: footer"></footer>
<script> ... </script>
</body>
</html>
api를 이용해 닉네임 중복 JS이다 해당 api로 이 회원의 id와 회원이 입력한 닉네임 값이 전달된다.
function nicknameModify(){
$.ajax({
type :"post",
url :"/api/nicknameModify",
data : {"id" : id.val(), "nickname" : nickname.val()},
dataType : "JSON",
success : function(result){
if(result.result == "0"){
$('.nickname_ok').css({"display" : "inline-block","color" : "blue"});
$('.nickname_already').css("display", "none");
nicknameCheck = 1;
}else{
$('.nickname_ok').css("display","none");
$('.nickname_already').css({"display" :"inline-block", "color" : "red"});
nicknameCheck = 0;
}
},
error : function(){
alert("ajax 실행 실패");
}
});
}
이후 아래와 같은 서비스를 거쳐 map형식으로 값을 리턴한다.
//닉네임 수정 중복 검사
public HashMap<String, Object> nicknameModify(String nickname, Long id) {
List<String> findNickname = memberQueryRepository.findExistNickname(id).getResults();
HashMap<String, Object> map = new HashMap<>();
map.put("result", findNickname.contains(nickname));
return map;
}
이 때 닉네임 체크시 본인의 닉네임은 제외 하고 체크해야한다. 만일 본인것도 포함해서 체크하게된다면 사용자의 닉네임은 언제나 중복일 것이다. 이는 QueryDsl을 이용하여 작성하였다.
//본인것 제외한 닉네임 찾기
public QueryResults<String> findExistNickname(Long id) {
return queryFactory.select(QMemberEntity.memberEntity.nickname)
.from(QMemberEntity.memberEntity)
.where(QMemberEntity.memberEntity.id.ne(id))
.fetchResults();
}
닉네임 중복 시
사용가능 닉네임
$(document).ready(function () {
const now = new Date();
const nowYear = now.getFullYear();
const nowAge_input = nowYear - $("#year").val() + 1;
const nowAge = nowYear - $("#year option:selected").val() + 1;
$("#age").val(nowAge);
$("#age_input").val(nowAge_input);
});
이후 수정 버튼을 누를시 validate 체크 후 이상이 없다면 해당 form의 값들이 post형식으로 전달되어 회원 정보가 업데이트 된다.