티스토리 뷰

- 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');
    }
};

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/04   »
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
글 보관함