티스토리 뷰
- memberController.java
package dev.shlee.studymemberbbs.controllers;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import java.awt.*;
@Controller
@RequestMapping(value = "/member")
public class MemberController {
@RequestMapping(value = "register", method = RequestMethod.GET,
produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView getRegister() {
ModelAndView modelAndView = new ModelAndView("member/register");
return modelAndView;
}
@RequestMapping(value = "/email",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody // 문자열 그대로 나온다.
public String postEmail(@RequestParam(value = "email") String email) {
System.out.println(email);
return "";
}
}
- register.css
@charset "UTF-8";
body > .main > .content {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
margin-top: 5rem;
margin-bottom: 5rem;
}
body > .main > .content > .form {
width: 100%;
max-width: 40rem;
align-items: stretch;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
body > .main > .content > .form > .title-container {
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
margin-bottom: 3.75rem;
}
body > .main > .content > .form > .title-container > .title {
flex: 1;
font-size: 2rem;
font-weight: 500;
}
body > .main > .content > .form > .title-container > .step-container {
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
}
body > .main > .content > .form > .title-container > .step-container > .step {
width: 2rem;
height: 2rem;
align-items: center;
border: 0.125rem solid rgb(213, 216, 220);
border-radius: 50%;
box-sizing: border-box;
color: rgb(128, 139, 150);
display: flex;
flex-direction: row;
font-size: 0.95rem;
font-weight: 600;
justify-content: center;
}
body > .main > .content > .form.step1 > .title-container > .step-container > .step.step1,
body > .main > .content > .form.step2 > .title-container > .step-container > .step.step2,
body > .main > .content > .form.step3 > .title-container > .step-container > .step.step3 {
background-color: #0b65cc;
color: rgb(255, 255, 255);
}
body > .main > .content > .form.step2 > .title-container > .step-container > .step.step1,
body > .main > .content > .form.step3 > .title-container > .step-container > .step.step1,
body > .main > .content > .form.step3 > .title-container > .step-container > .step.step2 {
background-color: rgb(117, 141, 218);
border: 0.125rem solid rgb(213, 216, 220);
color: rgb(171, 178, 185);
}
body > .main > .content > .form.step2 > .title-container > .step-container > .step.step1 + line,
body > .main > .content > .form.step3 > .title-container > .step-container > .step.step1 + line,
body > .main > .content > .form.step3 > .title-container > .step-container > .step.step2 + line {
background-color: rgb(213, 216, 220);
}
body > .main > .content > .form > .title-container > .step-container > .line {
width: 0.375rem;
height: 0.125rem;
background-color: rgb(171, 178, 185);
}
body > .main > .content > .form > .title-container > .step-container > .text {
color: rgb(86, 101, 115);
font-size: 0.95rem;
margin-right: 0.75rem;
}
body > .main > .content > .form > .step {
align-items: stretch;
display: none;
flex-direction: column;
justify-content: flex-start;
}
body > .main > .content > .form.step1 > .step.step1,
body > .main > .content > .form.step2 > .step.step2,
body > .main > .content > .form.step3 > .step.step3 {
display: flex;
}
body > .main > .content > .form > .step.step1 > .label {
margin-bottom: 1.5rem;
}
body > .main > .content > .form > .step.step1 > .label > .title {
font-size: 1.5rem;
font-weight: 500;
margin-bottom: 0.75rem;
}
body > .main > .content > .form > .step.step1 > .label > .title::before {
top: 0.0625rem;
width: 1rem;
height: 1rem;
background-color: rgb(171, 178, 185);
border-radius: 50%;
content: '';
display: inline-block;
margin-right: 0.625rem;
position: relative;
}
body > .main > .content > .form > .step.step1 > .label > .term {
height: 17.5rem;
resize: none;
}
body > .main > .content > .form > .step.step2 > .table {
/*max-width: 40rem;*/
/*align-self: center;*/
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > th {
padding-right: 1rem;
text-align: right;
white-space: nowrap;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr:not(:last-child) > th,
body > .main > .content > .form > .step.step2 > .table > tbody > tr:not(:last-child) > td {
padding-bottom: 1rem;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .label.email,
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .label.address {
align-items: stretch;
display: flex;
flex-direction: row;
justify-content: flex-start;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .label {
display: block;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .label.email + .label.email,
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .label.password + .label.password,
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .label.address + .label.address {
margin-top: 0.375rem;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .label > input + input {
margin-left: 0.5rem;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .panel.address {
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 70%);
display: none;
position: fixed;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .panel.address.visible {
display: block;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .warning {
color: rgb(231, 76, 60);
display: none;
font-size: 90%;
margin-top: 0.5rem;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .warning.visible {
display: block;
}
body > .main > .content > .form > .step.step2 > .table > tbody > tr > td > .panel.address > .dialog {
top: 50%;
left: 50%;
width: 100%;
height: 100%;
max-width: 30rem;
max-height: 40rem;
background-color: rgb(255,255,255);
position: absolute;
transform: translate(-50%, -50%);
}
body > .main > .content > .form > .warning {
background-color: rgb(231, 76, 60);
border-radius: 0.5rem;
color: rgb(255, 255, 255);
margin-top: 2.5rem;
padding: 1rem 1.25rem;
align-items: center;
display: none;
flex-direction: row;
justify-content: center;
}
body > .main > .content > .form > .warning.visible {
display: flex;
}
body > .main > .content > .form > .warning > .text {
flex: 1;
margin-left: 0.875rem;
}
body > .main > .content > .form > .button-container {
align-items: center;
display: flex;
flex-direction: row;
justify-content: center;
}
body > .main > .content > .form > .button-container > .button {
background-color: rgb(128, 139, 150);
border-radius: 0.5rem;
color: rgb(255, 255, 255);
cursor: pointer;
padding: 1rem 4rem;
user-select: none;
}
body > .main > .content > .form > .button-container > .button:hover {
background-color: rgb(44, 62, 80);
}
body > .main > .content > .form > .button-container > .button:active {
background-color: rgb(86, 101, 115);
}
body > .main > .content > .form > .button-container > .button.green {
background-color: #0b65cc;
margin-top: 2rem;
}
body > .main > .content > .form > .button-container > .button.green:hover {
background-color: #0b5db9;
}
body > .main > .content > .form > .button-container > .button.green:active {
background-color: #0a52a4;
}
- register.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>스터디 :: 회원가입</title>
<link rel="stylesheet" th:href="@{/resources/stylesheets/common.css}">
<link rel="stylesheet" th:href="@{/member/resources/stylesheets/register.css}">
<script src="https://kit.fontawesome.com/282afddd53.js" crossorigin="anonymous"></script>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script defer th:src="@{/resources/scripts/common.js}"></script>
<script defer th:src="@{/member/resources/scripts/register.js}"></script>
</head>
<body>
<header class="--header"...>
<div class="--cover" id="cover">
<img alt="" class="--icon" th:src="@{/resources/images/loading.png}">
<span class="--text" rel="text">무언가를 불러오고 있습니다. 잠시만 기다려 주세요.</span>
</div>
<div class="--content">
<a class="--logo-container" th:href="@{/}">
<img class="--logo" th:src="@{/resources/images/apple-logo.png}">
</a>
<ul class="--menu --main">
<li class="--item">
<a class="--link" href="#">공지사항</a>
</li>
<li class="--item">
<a class="--link" href="#">자유게시판</a>
</li>
<li class="--item">
<a class="--link" href="#">Q&A</a>
</li>
</ul>
<ul class="--menu">
<li class="--item">
<a class="--link" href="#">로그인</a>
</li>
<li class="--item">
<a class="--link" href="#">회원가입</a>
</li>
</ul>
</div>
</header>
<main class="--main main">
<div class="--content content">
<form class="form step2" id="form">
<div class="title-container">
<h2 class="title">회원가입</h2>
<div class="step-container">
<span class="text" rel="stepText">약관 동의 및 개인정보 처리방침</span>
<span class="step step1">1</span>
<span class="line"></span>
<span class="step step2">2</span>
<span class="line"></span>
<span class="step step3">3</span>
</div>
</div>
<div class="step step1">
<label class="label">
<h2 class="title">서비스 이용약관</h2>
<textarea class="--object-input term" readonly>서비스 이용약관</textarea>
</label>
<label class="label">
<h2 class="title">개인정보 처리방침</h2>
<textarea class="--object-input term" readonly>개인정보 처리방침</textarea>
</label>
<label class="--object-check">
<input name="termAgree" type="checkbox">
<span class="--checkbox">
<i class="--icon fa-solid fa-check"></i>
</span>
<span class="--text">위 서비스 이용약관 및 개인정보 처리방침을 읽어보았고 이해하였으며 동의합니다.</span>
</label>
</div>
<div class="step step2">
<table class="table">
<tbody>
<tr>
<th>이메일</th>
<td>
<label class="label email">
<span hidden>이메일</span>
<input autofocus class="--object-input input" maxlength="50" name="email" placeholder="이메일 주소를 입력해 주세요." type="email">
<input class="--object-button" name="emailSend" value="인증번호 전송" type="button">
</label>
<label class="label email">
<span hidden>인증번호</span>
<input autofocus class="--object-input input" disabled maxlength="6" name="emailAuthCode" placeholder="인증번호를 입력해 주세요." type="text">
<input class="--object-button" name="emailVerify" disabled value="인증번호 확인" type="button">
</label>
<span class="warning" rel="emailWarning">이메일 주소를 입력해주세요.</span>
</td>
</tr>
<tr>
<th>비밀번호</th>
<td>
<label class="label password">
<span hidden>비밀번호</span>
<input autofocus class="--object-input input" maxlength="50" name="password" placeholder="비밀번호를 입력해 주세요." type="password">
</label>
<label class="label password">
<span hidden>비밀번호 재입력</span>
<input autofocus class="--object-input input" maxlength="50" name="passwordCheck" placeholder="비밀번호를 한 번 더 입력해 주세요." type="password">
</label>
</td>
</tr>
<tr>
<th>닉네임</th>
<td>
<label class="label">
<span hidden>닉네임</span>
<input autofocus class="--object-input input" maxlength="10" name="nickname" placeholder="닉네임을 입력해 주세요." type="text">
</label>
</td>
</tr>
<tr>
<th>이름</th>
<td>
<label class="label">
<span hidden>이름</span>
<input autofocus class="--object-input input" maxlength="5" name="name" placeholder="이름을 입력해 주세요." type="text">
</label>
</td>
</tr>
<tr>
<th>연락처</th>
<td>
<label class="label">
<span hidden>연락처</span>
<input autofocus class="--object-input input" maxlength="12" name="contact" placeholder="연락처를 ' - ' 없이 입력해 주세요." type="text">
</label>
</td>
</tr>
<tr>
<th>주소</th>
<td>
<label class="label address">
<span hidden>우편번호</span>
<input autofocus class="--object-input input" maxlength="6" name="addressPostal" placeholder="우편번호" readonly type="text">
<input class="--object-button" name="addressFind" value="주소 찾기" type="button">
</label>
<label class="label address">
<span hidden>기본 주소</span>
<input autofocus class="--object-input input" maxlength="100" name="addressPrimary" readonly placeholder="주소 찾기를 통해 주소를 입력해 주세요." type="text">
</label>
<label class="label address">
<span hidden>상세 주소</span>
<input autofocus class="--object-input input" maxlength="100" name="addressSecondary" placeholder="상세 주소를 입력해 주세요." type="text">
</label>
<div class="panel address" rel="addressFindPanel">
<div class="dialog" rel="addressFindPanelDialog"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="warning" rel="warning">
<i class="icon fa-solid fa-triangle-exclamation"></i>
<div class="text" rel="warningText"></div>
</div>
<div class="button-container">
<div class="button green" rel="nextButton">
<span class="text">
다음 <i class="fa-solid fa-chevron-right"></i>
</span>
</div>
</div>
</form>
</div>
</main>
<footer class="--footer">
<div class="--content">
발
</div>
</footer>
</body>
</html>
- register.js
const form = window.document.getElementById('form');
form.querySelector('[rel="nextButton"]').addEventListener('click', () => {
form.querySelector('[rel=warning]').classList.remove('visible'); //체크하고 다음하면 visible 클래스 삭제
if (form.classList.contains('step1')) {
if (!form['termAgree'].checked) {
form.querySelector('[rel="warningText"]').innerText = '서비스 이용약관 및 개인정보 처리방침을 읽고 동의해 주세요.';
form.querySelector('[rel="warning"]').classList.add('visible');
return;
}
form.querySelector('[rel="stepText"]').innerText = '개인정보 입력';
form.classList.remove('step1');
form.classList.add('step2');
} else if (form.classList.contains('step2')) {
form.querySelector('[rel="stepText"]').innerText = '회원가입 완료';
form.querySelector('[rel="nextButton"]').innerText = '로그인하러 가기';
form.classList.remove('step2');
form.classList.add('step3');
} else if (form.classList.contains('step3')) {
window.location.href = 'login';
}
});
form['emailSend'].addEventListener('click', () => {
form.querySelector('[rel="emailWarning"]').classList.remove('visible');
if (form['email'].value === '') {
form.querySelector('[rel="emailWarning"]').innerText = '이메일 주소를 입력해 주세요';
form.querySelector('[rel="emailWarning"]').classList.add('visible');
form['email'].focus();
return;
}
// 올바른 이메일인지 확인하는 정규식
if (!new RegExp('^(?=.{7,50})([\\da-zA-Z_.]{4,})@([\\da-z\\-]{2,}\\.)?([\\da-z\\-]{2,})\\.([a-z]{2,10})(\\.[a-z]{2})?$').test(form['email'].value)) {
form.querySelector('[rel="emailWarning"]').innerText = '올바른 이메일 주소를 입력해 주세요';
form.querySelector('[rel="emailWarning"]').classList.add('visible');
form['email'].focus();
return;
}
Cover.show('인증번호를 전송하고 있습니다.\n잠시만 기다려 주세요.');
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('email', form['email'].value);
xhr.open('POST', './email');
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
Cover.hide();
if (xhr.status >= 200 && xhr.status < 300) {
} else {
form.querySelector('[rel="emailWarning"]').innerText = '서버와 통신하지 못하였습니다. 다시 시도해 주세요.';
form.querySelector('[rel="emailWarning"]').classList.add('visible');
}
}
};
xhr.send(formData);
});
form['addressFind'].addEventListener('click', () => {
new daum.Postcode({
oncomplete: e => {
form.querySelector('[rel="addressFindPanel"]').classList.remove('visible');
form['addressPostal'].value = e['zonecode'];
form['addressPrimary'].value = e['address'];
form['addressSecondary'].value = '';
form['addressSecondary'].focus();
},
width: '100%',
height: '100%'
}).embed(form.querySelector('[rel="addressFindPanelDialog"]'));
form.querySelector('[rel="addressFindPanel"]').classList.add('visible');
});
form.querySelector('[rel="addressFindPanel"]').addEventListener('click', () => {
form.querySelector('[rel="addressFindPanel"]').classList.remove('visible');
});
- common.css
@charset "UTF-8";
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.6/dist/web/static/pretendard.css");
html {
font-family: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
font-weight: 400;
}
h1, h2, h3, h4, h5, h6 {
font: inherit;
margin: unset;
padding: unset;
}
input, select, textarea {
appearance: none;
background-color: unset;
border: unset;
color: unset;
font: inherit;
margin: unset;
outline: none;
padding: unset;
}
.--object-input {
width: 100%;
background-color: rgb(255, 255, 255);
border: 0.0625rem solid rgb(213, 216, 220);
border-radius: 0.375rem;
box-sizing: border-box;
padding: 0.625rem 0.875rem;
}
.--object-input:disabled {
background-color: rgb(234, 236, 238);
color: rgb(171, 178, 185);
}
.--object-check {
align-items: center;
cursor: pointer;
display: flex;
flex-direction: row;
justify-content: flex-start;
user-select: none;
}
.--cover {
top: 0;
left: 0;
width: 100%;
height: 100%;
align-items: center;
background-color: rgba(0, 0, 0, 80%);
color: rgb(255, 255, 255);
display: none;
flex-direction: column;
justify-content: center;
position: fixed;
z-index: 9;
}
.--cover.visible {
display: flex;
}
.--cover > .--icon {
width: 4rem;
height: 4rem;
animation-name: anim-cover-icon;
animation-duration: 1500ms; /*한바퀴 도는데 걸리는 시간*/
animation-iteration-count: infinite; /*애니메이션 반복횟수 무한대*/
animation-timing-function: linear; /*자연스럽게 돌기*/
pointer-events: none;
user-select: none;
-webkit-user-drag: none;
}
@keyframes anim-cover-icon {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.--cover > .--text {
max-width: 25rem;
font-size: 1.375rem;
line-height: 150%;
margin-top: 2rem;
pointer-events: none;
text-align: center;
user-select: none;
white-space: pre-wrap;
}
.--object-check > input[type=checkbox] {
display: none;
}
.--object-check > .--checkbox {
width: 1rem;
height: 1rem;
align-items: center;
background-color: rgb(255, 255, 255);
border-radius: 0.125rem;
color: rgb(255, 255, 255);
display: flex;
flex: 0 0 1.25;
/*flex-grow: 0;*/
/*flex-shrink: 0;*/
/*flex-basis: 1.125rem;*/
flex-direction: row;
justify-content: center;
margin-right: 0.75rem;
overflow: hidden;
}
.--object-check > input[type=checkbox]:checked ~ .--checkbox {
background-color: #9d9d9d;
}
.--object-check > .--checkbox > .--icon {
top: 0.0625rem;
font-size: 0.8rem;
position: relative;
}
.--object-button {
background-color: rgb(197, 193, 193);
color: rgb(255, 255, 255);
border-radius: 0.375rem;
box-sizing: border-box;
cursor: pointer;
padding: 0.625rem 0.875rem;
}
.--object-button:hover {
background-color: rgb(121, 122, 122);
}
.--object-button:active {
ackground-color: rgb(72, 73, 73);
}
.--object-button:disabled {
ackground-color: rgb(213, 216, 220);
color: rgb(171, 178, 185);
cursor: default;
pointer-events: none;
}
body {
top: 0;
left: 0;
width: 100%;
background-color: rgb(234, 236, 238);
color: rgb(23, 32, 42);
align-items: stretch;
display: flex;
flex-direction: column;
justify-content: flex-start;
line-height: 125%;
font-size: 1rem;
min-height: 100%;
margin: unset;
overflow: hidden auto;
position: absolute;
}
.--header,
.--main,
.--footer {
align-items: flex-start;
display: flex;
flex-direction: row;
justify-content: center;
}
body > .--header > .--content,
body > .--main > .--content,
body > .--footer > .--content {
width: 100%;
max-width: 70rem;
margin: 0 2rem;
}
body > .--header {
background-color: rgb(255, 255, 255);
border-bottom: 0.0625rem solid rgb(213, 216, 220);
box-shadow: 0 0 0.75rem 0.0625rem rgba(0, 0, 0, 10%);
}
body > .--header > .--content {
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
padding: 1rem 0;
}
body > .--header > .--content > .--logo-container > .--logo {
height: 5rem;
}
body > .--header > .--content > .--menu {
align-items: center;
display: flex;
flex-direction: row;
justify-content: flex-start;
list-style-type: none;
margin: unset;
padding: unset;
}
body > .--header > .--content > .--menu.--main {
flex: 1;
font-size: 1.5rem;
margin-left: 2.5rem;
}
body > .--header > .--content > .--menu > .--item + .--item {
margin-left: 1rem;
}
body > .--header > .--content > .--menu > .--item > .--link {
color: rgb(128, 139, 150);
text-decoration: none;
}
body > .--header > .--content > .--menu > .--item > .--link:hover {
color: rgb(86, 101, 115);
}
body > .--header > .--content > .--menu > .--item > .--link:active {
color: rgb(44, 62, 80);
}
body > .--main {
flex: 1;
}
body > .--footer {
background-color: rgb(255, 255, 255);
border-bottom: 0.0625rem solid rgb(213, 216, 220);
box-shadow: 0 0 0.5rem 0.0625rem rgba(0, 0, 0, 10%);
}
- common.js
const Cover = {
show: (text) => {
const cover = window.document.getElementById('cover');
cover.querySelector('[rel="text"]').innerText = text;
cover.classList.add('visible');
},
hide: () => {
window.document.getElementById('cover').classList.remove('visible');
}
};
'웹 개발 > SpringBoot' 카테고리의 다른 글
| [Spring Boot] Test 인증번호 6자리 (3) | 2022.11.02 |
|---|---|
| [Spring Boot] interface, XML, Service (2) | 2022.11.02 |
| [Spring Boot] 의존성 주입 (2) | 2022.11.02 |
| [String Boot, CSS] 폰트 적용 (2) | 2022.10.31 |
| [String Boot] 회원제, 게시판 환경 설정 (0) | 2022.10.31 |
댓글