아이디찾기 스타일 적용

This commit is contained in:
현성필
2025-11-15 15:44:55 +09:00
parent aae7a5ac89
commit 672dd24b9a
7 changed files with 746 additions and 373 deletions
+448 -1
View File
@@ -6571,7 +6571,6 @@ select.form-control {
} }
.login-page { .login-page {
min-height: calc(100vh - 140px);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -6855,6 +6854,454 @@ select.form-control {
font-size: 14px; font-size: 14px;
} }
} }
.account-recovery-page {
display: flex;
align-items: center;
justify-content: center;
background: #EDF9FE;
padding: 40px 20px;
position: relative;
min-height: calc(100vh - 200px);
margin-top: 60px;
margin-bottom: 60px;
}
.account-recovery-container {
width: 100%;
max-width: 540px;
margin: 0 auto;
position: relative;
}
.account-recovery-card {
background: transparent;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
@media (max-width: 576px) {
.account-recovery-card {
padding: 0 20px;
}
}
.account-recovery-logo {
width: 90px;
height: 90px;
margin-bottom: 24px;
}
.account-recovery-logo img {
width: 100%;
height: 100%;
object-fit: contain;
}
.account-recovery-title {
font-family: "Noto Sans KR", sans-serif;
font-size: 28px;
font-weight: 700;
color: #000000;
text-align: center;
margin: 0 0 32px 0;
line-height: 1.3;
}
@media (max-width: 576px) {
.account-recovery-title {
font-size: 24px;
margin-bottom: 24px;
}
}
.account-recovery-tabs {
display: flex;
gap: 0;
margin-bottom: 40px;
width: 100%;
background: #F8F9FA;
border-radius: 12px;
padding: 4px;
}
.account-recovery-tabs .tab-link {
flex: 1;
padding: 14px 24px;
font-family: "Noto Sans KR", sans-serif;
font-size: 16px;
font-weight: 600;
color: #64748B;
text-decoration: none;
text-align: center;
background: transparent;
border-radius: 8px;
transition: all 0.3s ease;
}
.account-recovery-tabs .tab-link:hover {
color: #0049B4;
background: rgba(0, 73, 180, 0.05);
}
.account-recovery-tabs .tab-link.active {
color: #FFFFFF;
background: #0049B4;
box-shadow: 0 2px 4px rgba(0, 73, 180, 0.2);
}
@media (max-width: 576px) {
.account-recovery-tabs .tab-link {
padding: 12px 16px;
font-size: 15px;
}
}
.account-alert {
width: 100%;
margin-bottom: 20px;
padding: 14px 18px;
border-radius: 12px;
font-family: "Noto Sans KR", sans-serif;
font-size: 15px;
display: flex;
align-items: center;
gap: 10px;
animation: slideDown 0.3s ease;
}
.account-alert i {
font-size: 18px;
flex-shrink: 0;
}
.account-alert.alert-error {
background: rgba(255, 107, 107, 0.1);
color: #FF6B6B;
border: 1px solid rgba(255, 107, 107, 0.3);
}
.account-alert.alert-success {
background: rgba(107, 207, 127, 0.1);
color: #6BCF7F;
border: 1px solid rgba(107, 207, 127, 0.3);
}
.account-alert.alert-info {
background: rgba(0, 73, 180, 0.1);
color: #0049B4;
border: 1px solid rgba(0, 73, 180, 0.3);
}
.account-recovery-form {
width: 100%;
margin-bottom: 0;
}
.account-recovery-form .form-group {
margin-bottom: 24px;
}
.account-recovery-form .form-group:last-of-type {
margin-bottom: 0;
}
.account-recovery-form .form-label {
display: block;
font-family: "Noto Sans KR", sans-serif;
font-size: 15px;
font-weight: 600;
color: #1A1A2E;
margin-bottom: 10px;
}
.account-recovery-form .form-input {
width: 100%;
height: 70px;
padding: 0 24px;
font-family: "Noto Sans KR", sans-serif;
font-size: 16px;
font-weight: 400;
color: #1A1A2E;
background: #FFFFFF;
border: 1px solid #DDDDDD;
border-radius: 12px;
outline: none;
transition: all 0.3s ease;
}
.account-recovery-form .form-input::placeholder {
color: #94A3B8;
}
.account-recovery-form .form-input:hover {
border-color: #CBD5E1;
}
.account-recovery-form .form-input:focus {
border-color: #0049B4;
box-shadow: 0 0 0 4px rgba(0, 73, 180, 0.08);
}
.account-recovery-form .form-input:disabled {
background: #F8F9FA;
border-color: #E2E8F0;
color: #94A3B8;
cursor: not-allowed;
}
.account-recovery-form .form-input.error {
border-color: #FF6B6B;
}
.account-recovery-form .form-select {
width: 100%;
height: 70px;
padding: 0 24px;
font-family: "Noto Sans KR", sans-serif;
font-size: 16px;
font-weight: 400;
color: #1A1A2E;
background: #FFFFFF;
border: 1px solid #DDDDDD;
border-radius: 12px;
outline: none;
cursor: pointer;
transition: all 0.3s ease;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%2364748B' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 24px center;
padding-right: 50px;
}
.account-recovery-form .form-select:hover {
border-color: #CBD5E1;
}
.account-recovery-form .form-select:focus {
border-color: #0049B4;
box-shadow: 0 0 0 4px rgba(0, 73, 180, 0.08);
}
.account-recovery-form .form-select:disabled {
background-color: #F8F9FA;
border-color: #E2E8F0;
color: #94A3B8;
cursor: not-allowed;
opacity: 0.7;
}
.account-recovery-form .form-select option {
padding: 12px;
font-size: 16px;
}
.phone-input-group {
display: flex;
align-items: center;
gap: 12px;
}
.phone-input-group .phone-prefix {
flex: 0 0 140px;
}
.phone-input-group .phone-middle,
.phone-input-group .phone-last {
flex: 1;
}
.phone-input-group .phone-separator {
font-size: 16px;
color: #64748B;
font-weight: 500;
}
@media (max-width: 576px) {
.phone-input-group .phone-prefix {
flex: 0 0 110px;
}
.phone-input-group .phone-separator {
font-size: 14px;
}
}
.auth-number-group {
margin-top: 24px;
}
.auth-input-group {
position: relative;
}
.auth-input-group .auth-input {
padding-right: 100px;
}
.auth-input-group .auth-timer {
position: absolute;
right: 24px;
top: 50%;
transform: translateY(-50%);
font-family: "Noto Sans KR", sans-serif;
font-size: 16px;
font-weight: 600;
color: #FF6B6B;
pointer-events: none;
}
@media (max-width: 576px) {
.auth-input-group .auth-timer {
font-size: 14px;
right: 16px;
}
}
.auth-request-button,
.auth-verify-button {
width: 100%;
height: 70px;
padding: 0 32px;
font-family: "Noto Sans KR", sans-serif;
font-size: 17px;
font-weight: 700;
color: #FFFFFF;
background: #0049B4;
border: none;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 16px;
line-height: 1;
}
.auth-request-button:hover,
.auth-verify-button:hover {
background: rgb(0, 62.6583333333, 154.5);
}
.auth-request-button:active,
.auth-verify-button:active {
background: rgb(0, 52.3166666667, 129);
}
.auth-request-button:disabled,
.auth-verify-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
@media (max-width: 576px) {
.auth-request-button,
.auth-verify-button {
height: 60px;
font-size: 16px;
}
}
.form-actions {
display: flex;
gap: 12px;
margin-top: 32px;
}
.form-actions .cancel-button,
.form-actions .submit-button {
flex: 1;
height: 70px;
padding: 0 32px;
font-family: "Noto Sans KR", sans-serif;
font-size: 17px;
font-weight: 700;
border: none;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
line-height: 1;
}
.form-actions .cancel-button {
color: #64748B;
background: #FFFFFF;
border: 2px solid #E2E8F0;
}
.form-actions .cancel-button:hover {
background: #F8F9FA;
border-color: #CBD5E1;
color: #475569;
}
.form-actions .cancel-button:active {
background: #F1F5F9;
border-color: #94A3B8;
}
.form-actions .submit-button {
color: #FFFFFF;
background: #0049B4;
}
.form-actions .submit-button:hover {
background: rgb(0, 62.6583333333, 154.5);
}
.form-actions .submit-button:active {
background: rgb(0, 52.3166666667, 129);
}
.form-actions .submit-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
@media (max-width: 576px) {
.form-actions {
flex-direction: column;
gap: 12px;
}
.form-actions .cancel-button,
.form-actions .submit-button {
height: 60px;
font-size: 16px;
}
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(237, 249, 254, 0.9);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
transition: opacity 0.3s ease;
}
.loading-overlay.active {
display: flex;
}
.loading-overlay .spinner {
width: 40px;
height: 40px;
border: 3px solid #DDDDDD;
border-top-color: #0049B4;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
.account-recovery-page {
padding: 20px 12px;
margin-top: 40px;
margin-bottom: 40px;
}
.account-recovery-container {
max-width: 100%;
}
}
@media (max-width: 576px) {
.account-recovery-logo {
width: 70px;
height: 70px;
margin-bottom: 20px;
}
.account-recovery-form .form-group {
margin-bottom: 20px;
}
.account-recovery-form .form-label {
font-size: 14px;
margin-bottom: 8px;
}
.account-recovery-form .form-input,
.account-recovery-form .form-select {
height: 60px;
padding: 0 20px;
font-size: 15px;
}
.auth-request-button,
.auth-verify-button {
margin-top: 12px;
}
.form-actions {
margin-top: 24px;
}
}
.signup-selection-page { .signup-selection-page {
min-height: calc(100vh - 140px); min-height: calc(100vh - 140px);
display: flex; display: flex;
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -41,6 +41,7 @@
@import 'pages/api-market'; @import 'pages/api-market';
@import 'pages/documentation'; @import 'pages/documentation';
@import 'pages/login'; @import 'pages/login';
@import 'pages/account-recovery';
@import 'pages/signup-selection'; @import 'pages/signup-selection';
@import 'pages/mypage'; @import 'pages/mypage';
@import 'pages/apikey-register'; @import 'pages/apikey-register';
@@ -3,7 +3,6 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
.login-page { .login-page {
min-height: calc(100vh - 140px); // Account for header and footer
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -1,415 +1,341 @@
<!doctype html> <!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}"> xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kjbank_base_layout}">
<body> <body>
<section layout:fragment="contentFragment" class="content"> <th:block layout:fragment="contentFragment">
<div class="content_wrap"> <div class="account-recovery-page">
<!-- 타이틀 --> <div class="account-recovery-container">
<div class="sub_title2" id="title-find-id"> <div class="account-recovery-card">
<h2 class="title add5">아이디 찾기</h2> <!-- Logo -->
<div class="account-recovery-logo">
<img th:src="@{/img/logo/logo_box.png}" alt="광주은행">
</div> </div>
<div class="inner i_cs10 h_inner2"> <!-- Header Title -->
<!-- 탭 메뉴 --> <h2 class="account-recovery-title">아이디 찾기</h2>
<div class="tab-wrap">
<div class="tabs">
<ul class="tab_nav tabm">
<li class="active"><a href="">아이디 찾기</a></li>
<li><a th:href="@{/account_recovery(tab='resetPassword')}">비밀번호 초기화</a></li>
</ul>
<!-- 아이디 찾기 폼 --> <!-- Tab Navigation -->
<div class="tab active"> <div class="account-recovery-tabs">
<div class="form_type"> <a href="#" class="tab-link active">아이디 찾기</a>
<form id="accountForm" role="form" name="accountForm" th:action="@{/find_id}" method="post"> <a th:href="@{/account_recovery(tab='resetPassword')}" class="tab-link">비밀번호 초기화</a>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> </div>
<input type="hidden" id="mobileNumber" name="mobileNumber" th:value="${mobileNumber}">
<!-- 성명 입력 --> <!-- Alert Messages -->
<div class="info1 p_top2"> <div id="alertContainer"></div>
<p class="title w_tit">
<span class="dot">성명</span>
</p>
<div class="info_line">
<div class="info_box1 w_inp4">
<input type="text" name="userName"
th:value="${userName}" class="common_input_type_1" placeholder="성명"
required>
</div>
</div>
</div>
<!-- 휴대폰 번호 입력 --> <!-- Account Recovery Form -->
<div class="info1 pt28"> <form id="accountForm" role="form" name="accountForm" th:action="@{/find_id}" method="post" class="account-recovery-form">
<p class="title w_tit"> <input type="hidden" id="mobileNumber" name="mobileNumber" th:value="${mobileNumber}">
<span class="dot">휴대폰 번호</span>
</p> <!-- Name Field -->
<div class="info_line"> <div class="form-group">
<div class="info_box1 w_inp5"> <label for="userName" class="form-label">성명</label>
<span class="input"> <input type="text"
<div class="custom_select ct_sc w_inp10 h_inp2 select_mobile_prefix" id="prefix_select"> id="userName"
<div class="select_selected active">선택</div> name="userName"
<ul class="common_selecttype select_hide select_items ct_sc2"> th:value="${userName}"
<li>010</li> class="form-input"
<li>011</li> placeholder="성명"
<li>016</li> required>
<li>017</li> </div>
<li>018</li>
</ul> <!-- Phone Number Fields -->
</div> <div class="form-group">
</span> <label class="form-label">휴대폰 번호</label>
<span class="dash">-</span> <div class="phone-input-group">
<span class="input"> <select id="phonePrefix" class="form-select phone-prefix" required>
<input type="tel" name="phoneMiddle" maxlength="4" class="common_input_type_1 w_inp11" <option value="">선택</option>
placeholder="앞자리" required> <option value="010">010</option>
</span> <option value="011">011</option>
<span class="dash">-</span> <option value="016">016</option>
<span class="input"> <option value="017">017</option>
<input type="tel" name="phoneLast" maxlength="4" class="common_input_type_1 w_inp11" <option value="018">018</option>
placeholder="뒷자리" required> </select>
</span> <span class="phone-separator">-</span>
</div> <input type="tel"
<div class="btn_check"> id="phoneMiddle"
<a class="common_btn_type_2 cbt btn_auth" name="phoneMiddle"
th:hx-post="@{/auth/request_auth_number}" maxlength="4"
hx-include="#mobileNumber" class="form-input phone-middle"
hx-target="#authNumberContainer" placeholder="0000"
hx-swap="none"> pattern="[0-9]*"
<span>인증번호 받기</span> inputmode="numeric"
</a> required>
</div> <span class="phone-separator">-</span>
</div> <input type="tel"
</div> id="phoneLast"
<!-- 인증번호 입력 --> name="phoneLast"
<div class="info1 pt28 auth-number-container" id="authNumberContainer"> maxlength="4"
<p class="title w_tit"> class="form-input phone-last"
<span class="dot">인증번호 입력</span> placeholder="0000"
</p> pattern="[0-9]*"
<div class="info_line"> inputmode="numeric"
<div class="info_box1 w_inp5"> required>
<div class="certify_box">
<input type="text" name="authNumber" id="authNumber" maxlength="6"
pattern="[0-9]*"
inputmode="numeric"
class="common_input_type_1 w_input auth-number-input"
placeholder="SMS 인증번호" required>
<span class="certify_time" id="certify_time">03:00</span>
</div>
</div>
<div class="btn_check">
<a class="common_btn_type_2 cbt btn_verify_auth"
th:hx-post="@{/auth/verify_auth_number}"
hx-include="#mobileNumber, #authNumber"
hx-target="#authNumberValidation"
hx-swap="none">
<span>인증번호 확인</span>
</a>
</div>
</div>
</div>
<div id="authNumberValidation"></div>
<!-- 제출 버튼 -->
<div class="btn_application m_top">
<a type="button" class="common_btn_type_1 gray btn_cancel"><span>취소</span></a>
<div class="btn_gap"></div>
<button type="button" class="common_btn_type_1" onclick="validateAndSubmit()"><span>아이디 찾기</span></button>
</div>
</form>
</div>
</div>
</div>
</div> </div>
</div>
<!-- Auth Number Request Button -->
<button type="button" class="auth-request-button" id="requestAuthButton">
인증번호 받기
</button>
<!-- Auth Number Input -->
<div class="form-group auth-number-group" id="authNumberGroup" style="display: none;">
<label for="authNumber" class="form-label">인증번호 입력</label>
<div class="auth-input-group">
<input type="text"
id="authNumber"
name="authNumber"
maxlength="6"
class="form-input auth-input"
placeholder="인증번호 6자리"
pattern="[0-9]*"
inputmode="numeric"
required>
<span class="auth-timer" id="authTimer">03:00</span>
</div>
</div>
<!-- Auth Number Verify Button -->
<button type="button" class="auth-verify-button" id="verifyAuthButton" style="display: none;">
인증번호 확인
</button>
<!-- Submit Buttons -->
<div class="form-actions">
<button type="button" class="cancel-button" id="cancelButton">취소</button>
<button type="submit" class="submit-button" id="submitButton">아이디 찾기</button>
</div>
</form>
<!-- Loading State -->
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
</div> </div>
</div>
</div> </div>
</section> </div>
</th:block>
</body> </body>
<th:block layout:fragment="contentScript"> <th:block layout:fragment="contentScript">
<script th:inline="javascript"> <script th:inline="javascript">
$(document).ready(function () { $(document).ready(function() {
const errorMsg = [[${error}]]; const errorMsg = [[${error}]];
if (errorMsg) {
showAlert(errorMsg, 'error');
}
if (errorMsg) { const paramError = new URL(window.location.href).searchParams.get('error');
customPopups.showAlert(errorMsg); if (paramError) {
} showAlert(decodeURIComponent(paramError), 'error');
}
});
// Flash Attribute로 전달되는 에러 메시지 처리 // Alert 표시 함수
const paramError = new URL(window.location.href).searchParams.get('error'); function showAlert(message, type = 'info') {
if (paramError) { const alertContainer = $('#alertContainer');
customPopups.showAlert(decodeURIComponent(paramError)); const alertClass = type === 'error' ? 'alert-error' : type === 'success' ? 'alert-success' : 'alert-info';
} const iconClass = type === 'error' ? 'fa-exclamation-circle' : type === 'success' ? 'fa-check-circle' : 'fa-info-circle';
});
</script>
<script th:inline="javascript">
function combineMobileNumber(evt) {
let form = $('#accountForm');
let prefix = form.find('.select_mobile_prefix .select_selected').text().trim();
let middle = form.find('input[name="phoneMiddle"]').val().trim();
let last = form.find('input[name="phoneLast"]').val().trim();
if (prefix === '선택' || !middle || !last) { const alertHtml = `
customPopups.showAlert('휴대폰 번호를 올바르게 입력해주세요.'); <div class="account-alert ${alertClass}">
evt.preventDefault(); <i class="fas ${iconClass}"></i>
return; <span>${message}</span>
} </div>
`;
let combinedNumber = `${prefix}-${middle}-${last}`; alertContainer.html(alertHtml);
let mobileNumberInput = document.getElementById('mobileNumber'); setTimeout(() => alertContainer.empty(), 5000);
if (mobileNumberInput) { }
mobileNumberInput.value = combinedNumber;
} // 휴대폰 번호 결합 함수
return combinedNumber; function combineMobileNumber() {
const prefix = $('#phonePrefix').val();
const middle = $('#phoneMiddle').val().trim();
const last = $('#phoneLast').val().trim();
if (!prefix || prefix === '' || !middle || !last) {
return null;
}
return `${prefix}-${middle}-${last}`;
}
// 타이머 변수
let countdownInterval;
let isAuthNumberRequested = false;
let isAuthVerified = false;
// 인증번호 타이머 시작
function startAuthTimer() {
clearInterval(countdownInterval);
let remainingTime = 180; // 3분
function updateTimer() {
if (remainingTime <= 0) {
clearInterval(countdownInterval);
$('#authTimer').text('시간 초과').css('color', '#FF6B6B');
showAlert('입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요.', 'error');
// 필드 활성화
$('#phonePrefix, #phoneMiddle, #phoneLast').prop('disabled', false);
$('#requestAuthButton').prop('disabled', false);
isAuthNumberRequested = false;
} else {
const minutes = Math.floor(remainingTime / 60);
const seconds = remainingTime % 60;
$('#authTimer').text(`${minutes}:${seconds.toString().padStart(2, '0')}`);
remainingTime--;
} }
}
// 폼 전체 유효성 검증 함수 updateTimer();
function validateAndSubmit() { countdownInterval = setInterval(updateTimer, 1000);
const form = document.getElementById('accountForm'); }
// 성명 검증 // 인증번호 요청 버튼
const userName = form.querySelector('input[name="userName"]').value.trim(); $('#requestAuthButton').on('click', function() {
if (!userName) { const mobileNumber = combineMobileNumber();
customPopups.showAlert('성명을 입력해주세요.');
return false;
}
// 휴대폰 번호 검증 if (!mobileNumber) {
const prefix = form.querySelector('.select_mobile_prefix .select_selected').textContent.trim(); showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
const phoneMiddle = form.querySelector('input[name="phoneMiddle"]').value.trim(); return;
const phoneLast = form.querySelector('input[name="phoneLast"]').value.trim(); }
if (prefix === '선택' || !phoneMiddle || !phoneLast) { $('#mobileNumber').val(mobileNumber);
customPopups.showAlert('휴대폰 번호를 올바르게 입력해주세요.'); $('#loadingOverlay').fadeIn(200);
return false;
}
// 인증번호 요청 여부 검증 $.ajax({
if (!window.isAuthNumberRequested) { url: '/auth/request_auth_number',
customPopups.showAlert('휴대폰 인증을 먼저 진행해주세요.'); type: 'POST',
return false; data: {
} mobileNumber: mobileNumber,
_csrf: $('input[name="_csrf"]').val()
},
success: function(response) {
$('#loadingOverlay').fadeOut(200);
// 인증 완료 여부 검증 if (response.valid) {
const authNumberInput = document.getElementById('authNumber'); showAlert(response.message || '인증번호가 발송되었습니다.', 'success');
if (!authNumberInput?.disabled) {
customPopups.showAlert('휴대폰 인증을 완료해주세요.');
return false;
}
// 모든 검증을 통과하면 휴대폰 번호 결합 후 form submit // 휴대폰 번호 필드 비활성화
combineMobileNumber({ $('#phonePrefix, #phoneMiddle, #phoneLast').prop('disabled', true);
preventDefault: () => { $('#requestAuthButton').prop('disabled', true);
}
});
// 세션 검증을 위한 파라미터 추가 // 인증번호 입력 영역 표시
const currentAction = form.getAttribute('action'); $('#authNumberGroup').slideDown(300);
form.setAttribute('action', currentAction + (currentAction.includes('?') ? '&' : '?') + 'showError=true'); $('#verifyAuthButton').slideDown(300);
form.submit(); isAuthNumberRequested = true;
startAuthTimer();
} else {
showAlert(response.message || '인증번호 발송에 실패했습니다.', 'error');
}
},
error: function() {
$('#loadingOverlay').fadeOut(200);
showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
} }
});
});
document.body.addEventListener('htmx:beforeRequest', function (evt) { // 인증번호 확인 버튼
combineMobileNumber(evt); $('#verifyAuthButton').on('click', function() {
}); if (!isAuthNumberRequested) {
showAlert('휴대폰 번호 인증을 먼저 진행해주세요.', 'error');
return;
}
document.body.addEventListener('htmx:configRequest', function (evt) { const authNumber = $('#authNumber').val().trim();
evt.detail.parameters['mobileNumber'] = combineMobileNumber(evt);
});
document.addEventListener('DOMContentLoaded', function () { if (authNumber.length !== 6) {
// 기존 휴대폰 번호 복원 showAlert('인증번호 6자리를 입력해주세요.', 'error');
const mobileNumber = document.getElementById('mobileNumber')?.value; return;
if (mobileNumber) { }
const mobileParts = mobileNumber.split('-');
if (mobileParts.length === 3) {
const PrefixSelect = document.querySelector('#prefix_select .select_selected');
if (PrefixSelect) {
PrefixSelect.textContent = mobileParts[0];
}
const orgMiddleInput = document.querySelector('[name="phoneMiddle"]');
if (orgMiddleInput) {
orgMiddleInput.value = mobileParts[1];
}
const orgLastInput = document.querySelector('[name="phoneLast"]');
if (orgLastInput) {
orgLastInput.value = mobileParts[2];
}
}
}
activateSelect(); const mobileNumber = $('#mobileNumber').val();
let countdownInterval; $('#loadingOverlay').fadeIn(200);
window.isAuthNumberRequested = false;
function startAuthNumberTimer() { $.ajax({
clearInterval(countdownInterval); url: '/auth/verify_auth_number',
let remainingTime = 180; // 3분 type: 'POST',
data: {
mobileNumber: mobileNumber,
authNumber: authNumber,
_csrf: $('input[name="_csrf"]').val()
},
success: function(response) {
$('#loadingOverlay').fadeOut(200);
function updateRemainingTime() { if (response.valid) {
if (remainingTime <= 0) { showAlert(response.message || '인증이 완료되었습니다.', 'success');
clearInterval(countdownInterval);
let timerElement = document.getElementById('certify_time');
if (timerElement) {
timerElement.textContent = '입력 시간이 초과되었습니다.';
}
customPopups.showAlert('입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요.');
let form = $('#accountForm'); // 인증번호 입력 필드 비활성화
let mobilePrefix = form.find('.select_mobile_prefix'); $('#authNumber').prop('disabled', true);
let phoneMiddle = form.find('input[name="phoneMiddle"]'); $('#verifyAuthButton').prop('disabled', true);
let phoneLast = form.find('input[name="phoneLast"]');
let authButton = document.querySelector('.btn_auth');
let verifyAuthButton = document.querySelector('.btn_verify_auth');
let authNumberInput = document.getElementById('authNumber');
// 입력 필드 활성화 clearInterval(countdownInterval);
mobilePrefix.removeClass('disabled'); $('#authTimer').text('인증 완료').css('color', '#6BCF7F');
phoneMiddle.prop('readonly', false);
phoneLast.prop('readonly', false);
if (authButton) {
authButton.disabled = false;
}
if (verifyAuthButton) {
verifyAuthButton.disabled = true;
}
if (authNumberInput) {
authNumberInput.readOnly = true;
}
window.isAuthNumberRequested = false;
} else {
let minutes = Math.floor(remainingTime / 60);
let seconds = remainingTime % 60;
let timerElement = document.getElementById('certify_time');
if (timerElement) {
timerElement.textContent = `남은 시간: ${minutes}:${seconds.toString().padStart(2, '0')}`;
}
remainingTime--;
}
}
updateRemainingTime(); isAuthVerified = true;
countdownInterval = setInterval(updateRemainingTime, 1000); } else {
} showAlert(response.message || '인증번호가 일치하지 않습니다.', 'error');
}
},
error: function() {
$('#loadingOverlay').fadeOut(200);
showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
}
});
});
function handleAuthNumberRequest(response) { // 폼 제출
if (response.valid) { $('#accountForm').on('submit', function(e) {
let form = $('#accountForm'); e.preventDefault();
let mobilePrefix = form.find('.select_mobile_prefix');
let phoneMiddle = form.find('input[name="phoneMiddle"]');
let phoneLast = form.find('input[name="phoneLast"]');
let authButton = document.querySelector('.btn_auth');
mobilePrefix.addClass('disabled'); const userName = $('#userName').val().trim();
phoneMiddle.prop('disabled', true); if (!userName) {
phoneLast.prop('disabled', true); showAlert('성명을 입력해주세요.', 'error');
if (authButton) { return;
authButton.disabled = true; }
}
window.isAuthNumberRequested = true; const mobileNumber = combineMobileNumber();
startAuthNumberTimer(); if (!mobileNumber) {
customPopups.showAlert(response.message || '인증번호가 발송되었습니다.'); showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
} else { return;
customPopups.showAlert(response.message || '알 수 없는 오류가 발생했습니다.'); }
let authButton = document.querySelector('.btn_auth');
if (authButton) {
authButton.disabled = false;
}
}
}
function handleAuthNumberVerification(response) { if (!isAuthNumberRequested) {
customPopups.showAlert(response.message); showAlert('휴대폰 인증을 먼저 진행해주세요.', 'error');
if (response.valid) { return;
let authNumberInput = document.getElementById('authNumber'); }
let verifyAuthButton = document.querySelector('.btn_verify_auth');
let timerElement = document.getElementById('certify_time');
if (authNumberInput) { if (!isAuthVerified) {
authNumberInput.readOnly = true; showAlert('휴대폰 인증을 완료해주세요.', 'error');
authNumberInput.disabled = true; return;
} }
if (verifyAuthButton) {
verifyAuthButton.disabled = true;
}
clearInterval(countdownInterval); $('#mobileNumber').val(mobileNumber);
if (timerElement) { this.submit();
timerElement.textContent = '인증 완료'; });
}
window.isAuthVerified = true;
document.dispatchEvent(new Event('authVerified'));
}
}
// HTMX 응답 처리 // 취소 버튼
document.body.addEventListener('htmx:afterSwap', function (event) { $('#cancelButton').on('click', function() {
let targetId = event.detail.target ? event.detail.target.id : null; window.location.href = '/login';
if (targetId && ['authNumberContainer', 'authNumberValidation'].includes(targetId)) { });
let response;
try {
response = typeof event.detail.xhr.response === 'string'
? JSON.parse(event.detail.xhr.response)
: event.detail.xhr.response;
} catch (e) {
console.error('Failed to parse response as JSON:', event.detail.xhr.response);
customPopups.showAlert('서버에서 예상치 못한 응답을 받았습니다.');
return;
}
switch (targetId) { // 기존 휴대폰 번호 복원
case 'authNumberContainer': $(document).ready(function() {
handleAuthNumberRequest(response); const savedMobileNumber = $('#mobileNumber').val();
break; if (savedMobileNumber) {
case 'authNumberValidation': const parts = savedMobileNumber.split('-');
handleAuthNumberVerification(response); if (parts.length === 3) {
break; $('#phonePrefix').val(parts[0]);
} $('#phoneMiddle').val(parts[1]);
} $('#phoneLast').val(parts[2]);
}); }
}
document.body.addEventListener('htmx:responseError', function (event) { });
customPopups.showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.'); </script>
});
// 인증번호 확인 버튼 이벤트
let verifyAuthButton = document.querySelector('.btn_verify_auth');
if (verifyAuthButton) {
verifyAuthButton.addEventListener('click', function (e) {
if (!window.isAuthNumberRequested) {
e.preventDefault();
customPopups.showAlert('휴대폰 번호 인증을 먼저 진행해주세요.');
return;
}
let form = $('#accountForm');
let authNumberInput = form.find('.auth-number-input');
let authNumber = authNumberInput.val().trim();
if (authNumber.length !== 6) {
e.preventDefault();
customPopups.showAlert('입력된 인증번호가 유효하지 않습니다.');
return;
}
});
}
// 취소 버튼 이벤트
let cancelButton = document.querySelector('.btn_cancel');
if (cancelButton) {
cancelButton.addEventListener('click', function () {
location.href = '/login';
});
}
// 폼 제출 이벤트 처리
const accountForm = document.getElementById('accountForm');
if (accountForm) {
accountForm.addEventListener('submit', function (e) {
e.preventDefault();
validateAndSubmit();
});
}
});
</script>
</th:block> </th:block>
</html> </html>