아이디찾기 스타일 적용

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 {
min-height: calc(100vh - 140px);
display: flex;
align-items: center;
justify-content: center;
@@ -6855,6 +6854,454 @@ select.form-control {
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 {
min-height: calc(100vh - 140px);
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/documentation';
@import 'pages/login';
@import 'pages/account-recovery';
@import 'pages/signup-selection';
@import 'pages/mypage';
@import 'pages/apikey-register';
@@ -3,7 +3,6 @@
// -----------------------------------------------------------------------------
.login-page {
min-height: calc(100vh - 140px); // Account for header and footer
display: flex;
align-items: center;
justify-content: center;
@@ -1,415 +1,341 @@
<!doctype html>
<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>
<section layout:fragment="contentFragment" class="content">
<div class="content_wrap">
<!-- 타이틀 -->
<div class="sub_title2" id="title-find-id">
<h2 class="title add5">아이디 찾기</h2>
<th:block layout:fragment="contentFragment">
<div class="account-recovery-page">
<div class="account-recovery-container">
<div class="account-recovery-card">
<!-- Logo -->
<div class="account-recovery-logo">
<img th:src="@{/img/logo/logo_box.png}" alt="광주은행">
</div>
<div class="inner i_cs10 h_inner2">
<!-- 탭 메뉴 -->
<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>
<!-- Header Title -->
<h2 class="account-recovery-title">아이디 찾기</h2>
<!-- 아이디 찾기 폼 -->
<div class="tab active">
<div class="form_type">
<form id="accountForm" role="form" name="accountForm" th:action="@{/find_id}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<input type="hidden" id="mobileNumber" name="mobileNumber" th:value="${mobileNumber}">
<!-- Tab Navigation -->
<div class="account-recovery-tabs">
<a href="#" class="tab-link active">아이디 찾기</a>
<a th:href="@{/account_recovery(tab='resetPassword')}" class="tab-link">비밀번호 초기화</a>
</div>
<!-- 성명 입력 -->
<div class="info1 p_top2">
<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>
<!-- Alert Messages -->
<div id="alertContainer"></div>
<!-- 휴대폰 번호 입력 -->
<div class="info1 pt28">
<p class="title w_tit">
<span class="dot">휴대폰 번호</span>
</p>
<div class="info_line">
<div class="info_box1 w_inp5">
<span class="input">
<div class="custom_select ct_sc w_inp10 h_inp2 select_mobile_prefix" id="prefix_select">
<div class="select_selected active">선택</div>
<ul class="common_selecttype select_hide select_items ct_sc2">
<li>010</li>
<li>011</li>
<li>016</li>
<li>017</li>
<li>018</li>
</ul>
</div>
</span>
<span class="dash">-</span>
<span class="input">
<input type="tel" name="phoneMiddle" maxlength="4" class="common_input_type_1 w_inp11"
placeholder="앞자리" required>
</span>
<span class="dash">-</span>
<span class="input">
<input type="tel" name="phoneLast" maxlength="4" class="common_input_type_1 w_inp11"
placeholder="뒷자리" required>
</span>
</div>
<div class="btn_check">
<a class="common_btn_type_2 cbt btn_auth"
th:hx-post="@{/auth/request_auth_number}"
hx-include="#mobileNumber"
hx-target="#authNumberContainer"
hx-swap="none">
<span>인증번호 받기</span>
</a>
</div>
</div>
</div>
<!-- 인증번호 입력 -->
<div class="info1 pt28 auth-number-container" id="authNumberContainer">
<p class="title w_tit">
<span class="dot">인증번호 입력</span>
</p>
<div class="info_line">
<div class="info_box1 w_inp5">
<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>
<!-- Account Recovery Form -->
<form id="accountForm" role="form" name="accountForm" th:action="@{/find_id}" method="post" class="account-recovery-form">
<input type="hidden" id="mobileNumber" name="mobileNumber" th:value="${mobileNumber}">
<!-- Name Field -->
<div class="form-group">
<label for="userName" class="form-label">성명</label>
<input type="text"
id="userName"
name="userName"
th:value="${userName}"
class="form-input"
placeholder="성명"
required>
</div>
<!-- Phone Number Fields -->
<div class="form-group">
<label class="form-label">휴대폰 번호</label>
<div class="phone-input-group">
<select id="phonePrefix" class="form-select phone-prefix" required>
<option value="">선택</option>
<option value="010">010</option>
<option value="011">011</option>
<option value="016">016</option>
<option value="017">017</option>
<option value="018">018</option>
</select>
<span class="phone-separator">-</span>
<input type="tel"
id="phoneMiddle"
name="phoneMiddle"
maxlength="4"
class="form-input phone-middle"
placeholder="0000"
pattern="[0-9]*"
inputmode="numeric"
required>
<span class="phone-separator">-</span>
<input type="tel"
id="phoneLast"
name="phoneLast"
maxlength="4"
class="form-input phone-last"
placeholder="0000"
pattern="[0-9]*"
inputmode="numeric"
required>
</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>
</section>
</div>
</th:block>
</body>
<th:block layout:fragment="contentScript">
<script th:inline="javascript">
$(document).ready(function () {
const errorMsg = [[${error}]];
<script th:inline="javascript">
$(document).ready(function() {
const errorMsg = [[${error}]];
if (errorMsg) {
showAlert(errorMsg, 'error');
}
if (errorMsg) {
customPopups.showAlert(errorMsg);
}
const paramError = new URL(window.location.href).searchParams.get('error');
if (paramError) {
showAlert(decodeURIComponent(paramError), 'error');
}
});
// Flash Attribute로 전달되는 에러 메시지 처리
const paramError = new URL(window.location.href).searchParams.get('error');
if (paramError) {
customPopups.showAlert(decodeURIComponent(paramError));
}
});
</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();
// Alert 표시 함수
function showAlert(message, type = 'info') {
const alertContainer = $('#alertContainer');
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';
if (prefix === '선택' || !middle || !last) {
customPopups.showAlert('휴대폰 번호를 올바르게 입력해주세요.');
evt.preventDefault();
return;
}
const alertHtml = `
<div class="account-alert ${alertClass}">
<i class="fas ${iconClass}"></i>
<span>${message}</span>
</div>
`;
let combinedNumber = `${prefix}-${middle}-${last}`;
let mobileNumberInput = document.getElementById('mobileNumber');
if (mobileNumberInput) {
mobileNumberInput.value = combinedNumber;
}
return combinedNumber;
alertContainer.html(alertHtml);
setTimeout(() => alertContainer.empty(), 5000);
}
// 휴대폰 번호 결합 함수
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--;
}
}
// 폼 전체 유효성 검증 함수
function validateAndSubmit() {
const form = document.getElementById('accountForm');
updateTimer();
countdownInterval = setInterval(updateTimer, 1000);
}
// 성명 검증
const userName = form.querySelector('input[name="userName"]').value.trim();
if (!userName) {
customPopups.showAlert('성명을 입력해주세요.');
return false;
}
// 인증번호 요청 버튼
$('#requestAuthButton').on('click', function() {
const mobileNumber = combineMobileNumber();
// 휴대폰 번호 검증
const prefix = form.querySelector('.select_mobile_prefix .select_selected').textContent.trim();
const phoneMiddle = form.querySelector('input[name="phoneMiddle"]').value.trim();
const phoneLast = form.querySelector('input[name="phoneLast"]').value.trim();
if (!mobileNumber) {
showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
return;
}
if (prefix === '선택' || !phoneMiddle || !phoneLast) {
customPopups.showAlert('휴대폰 번호를 올바르게 입력해주세요.');
return false;
}
$('#mobileNumber').val(mobileNumber);
$('#loadingOverlay').fadeIn(200);
// 인증번호 요청 여부 검증
if (!window.isAuthNumberRequested) {
customPopups.showAlert('휴대폰 인증을 먼저 진행해주세요.');
return false;
}
$.ajax({
url: '/auth/request_auth_number',
type: 'POST',
data: {
mobileNumber: mobileNumber,
_csrf: $('input[name="_csrf"]').val()
},
success: function(response) {
$('#loadingOverlay').fadeOut(200);
// 인증 완료 여부 검증
const authNumberInput = document.getElementById('authNumber');
if (!authNumberInput?.disabled) {
customPopups.showAlert('휴대폰 인증을 완료해주세요.');
return false;
}
if (response.valid) {
showAlert(response.message || '인증번호가 발송되었습니다.', 'success');
// 모든 검증을 통과하면 휴대폰 번호 결합 후 form submit
combineMobileNumber({
preventDefault: () => {
}
});
// 휴대폰 번호 필드 비활성화
$('#phonePrefix, #phoneMiddle, #phoneLast').prop('disabled', true);
$('#requestAuthButton').prop('disabled', true);
// 세션 검증을 위한 파라미터 추가
const currentAction = form.getAttribute('action');
form.setAttribute('action', currentAction + (currentAction.includes('?') ? '&' : '?') + 'showError=true');
// 인증번호 입력 영역 표시
$('#authNumberGroup').slideDown(300);
$('#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) {
evt.detail.parameters['mobileNumber'] = combineMobileNumber(evt);
});
const authNumber = $('#authNumber').val().trim();
document.addEventListener('DOMContentLoaded', function () {
// 기존 휴대폰 번호 복원
const mobileNumber = document.getElementById('mobileNumber')?.value;
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];
}
}
}
if (authNumber.length !== 6) {
showAlert('인증번호 6자리를 입력해주세요.', 'error');
return;
}
activateSelect();
let countdownInterval;
window.isAuthNumberRequested = false;
const mobileNumber = $('#mobileNumber').val();
$('#loadingOverlay').fadeIn(200);
function startAuthNumberTimer() {
clearInterval(countdownInterval);
let remainingTime = 180; // 3분
$.ajax({
url: '/auth/verify_auth_number',
type: 'POST',
data: {
mobileNumber: mobileNumber,
authNumber: authNumber,
_csrf: $('input[name="_csrf"]').val()
},
success: function(response) {
$('#loadingOverlay').fadeOut(200);
function updateRemainingTime() {
if (remainingTime <= 0) {
clearInterval(countdownInterval);
let timerElement = document.getElementById('certify_time');
if (timerElement) {
timerElement.textContent = '입력 시간이 초과되었습니다.';
}
customPopups.showAlert('입력 시간이 초과되었습니다. 인증번호를 다시 발송해주세요.');
if (response.valid) {
showAlert(response.message || '인증이 완료되었습니다.', 'success');
let form = $('#accountForm');
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');
let verifyAuthButton = document.querySelector('.btn_verify_auth');
let authNumberInput = document.getElementById('authNumber');
// 인증번호 입력 필드 비활성화
$('#authNumber').prop('disabled', true);
$('#verifyAuthButton').prop('disabled', true);
// 입력 필드 활성화
mobilePrefix.removeClass('disabled');
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--;
}
}
clearInterval(countdownInterval);
$('#authTimer').text('인증 완료').css('color', '#6BCF7F');
updateRemainingTime();
countdownInterval = setInterval(updateRemainingTime, 1000);
}
isAuthVerified = true;
} else {
showAlert(response.message || '인증번호가 일치하지 않습니다.', 'error');
}
},
error: function() {
$('#loadingOverlay').fadeOut(200);
showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.', 'error');
}
});
});
function handleAuthNumberRequest(response) {
if (response.valid) {
let form = $('#accountForm');
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');
// 폼 제출
$('#accountForm').on('submit', function(e) {
e.preventDefault();
mobilePrefix.addClass('disabled');
phoneMiddle.prop('disabled', true);
phoneLast.prop('disabled', true);
if (authButton) {
authButton.disabled = true;
}
const userName = $('#userName').val().trim();
if (!userName) {
showAlert('성명을 입력해주세요.', 'error');
return;
}
window.isAuthNumberRequested = true;
startAuthNumberTimer();
customPopups.showAlert(response.message || '인증번호가 발송되었습니다.');
} else {
customPopups.showAlert(response.message || '알 수 없는 오류가 발생했습니다.');
let authButton = document.querySelector('.btn_auth');
if (authButton) {
authButton.disabled = false;
}
}
}
const mobileNumber = combineMobileNumber();
if (!mobileNumber) {
showAlert('휴대폰 번호를 올바르게 입력해주세요.', 'error');
return;
}
function handleAuthNumberVerification(response) {
customPopups.showAlert(response.message);
if (response.valid) {
let authNumberInput = document.getElementById('authNumber');
let verifyAuthButton = document.querySelector('.btn_verify_auth');
let timerElement = document.getElementById('certify_time');
if (!isAuthNumberRequested) {
showAlert('휴대폰 인증을 먼저 진행해주세요.', 'error');
return;
}
if (authNumberInput) {
authNumberInput.readOnly = true;
authNumberInput.disabled = true;
}
if (verifyAuthButton) {
verifyAuthButton.disabled = true;
}
if (!isAuthVerified) {
showAlert('휴대폰 인증을 완료해주세요.', 'error');
return;
}
clearInterval(countdownInterval);
if (timerElement) {
timerElement.textContent = '인증 완료';
}
window.isAuthVerified = true;
document.dispatchEvent(new Event('authVerified'));
}
}
$('#mobileNumber').val(mobileNumber);
this.submit();
});
// HTMX 응답 처리
document.body.addEventListener('htmx:afterSwap', function (event) {
let targetId = event.detail.target ? event.detail.target.id : null;
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;
}
// 취소 버튼
$('#cancelButton').on('click', function() {
window.location.href = '/login';
});
switch (targetId) {
case 'authNumberContainer':
handleAuthNumberRequest(response);
break;
case 'authNumberValidation':
handleAuthNumberVerification(response);
break;
}
}
});
document.body.addEventListener('htmx:responseError', function (event) {
customPopups.showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.');
});
// 인증번호 확인 버튼 이벤트
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>
// 기존 휴대폰 번호 복원
$(document).ready(function() {
const savedMobileNumber = $('#mobileNumber').val();
if (savedMobileNumber) {
const parts = savedMobileNumber.split('-');
if (parts.length === 3) {
$('#phonePrefix').val(parts[0]);
$('#phoneMiddle').val(parts[1]);
$('#phoneLast').val(parts[2]);
}
}
});
</script>
</th:block>
</html>