회원가입 디자인 적용

This commit is contained in:
현성필
2025-11-15 18:38:03 +09:00
parent cadac012e6
commit 42100e8a1a
18 changed files with 3088 additions and 1398 deletions
@@ -1,102 +1,83 @@
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}"
xmlns:hx-on="http://www.w3.org/1999/xhtml">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout/kbank_base_layout}">
<body>
<th:block th:fragment="commonUserInfo">
<div class="info1 pt28">
<p class="title">
<span class="dot">이메일 아이디</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" id="userId" class="common_input_type_1 w_inp"
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
이메일 아이디
</label>
<div class="org-form-input-wrapper">
<div class="org-compound-input">
<input type="text" id="userId" class="org-form-input"
th:classappend="${isInvited ? 'input-readonly' : ''}"
maxlength="30"
th:value="${emailId}"
th:readonly="${isInvited}"
th:placeholder="#{portalUser.Register.email}">
<span class="special_t">@</span>
<input type="text" id="domain" class="common_input_type_1 w_inp2"
<span class="separator">@</span>
<input type="text" id="domain" class="org-form-input"
th:classappend="${isInvited ? 'input-readonly' : ''}"
maxlength="50"
th:value="${domain}"
th:readonly="${isInvited}"
th:placeholder="#{portalUser.Register.domain}">
<input type="hidden" id="loginId" name="loginId" th:value="${loginId}">
<input type="hidden" name="registrationType" th:value="${registrationType}"/>
<input type="hidden" id="registrationScenario" name="registrationScenario" value="new"/>
</div>
<div class="btn_check">
<a class="common_btn_type_2 cbt btn_check_email"
th:classappend="${isInvited ? 'hidden' : ''}"
th:hx-post="@{/check_id}"
hx-include="#csrf, #loginId, [name='registrationType']"
hx-target="#email-validation"
hx-swap="none">
<span>중복체크</span>
</a>
</div>
<input type="hidden" id="loginId" name="loginId" th:value="${loginId}">
<input type="hidden" name="registrationType" th:value="${registrationType}"/>
<input type="hidden" id="registrationScenario" name="registrationScenario" value="new"/>
<button type="button" class="org-btn org-btn-check btn_check_email"
th:classappend="${isInvited ? 'hidden' : ''}">
중복체크
</button>
<div id="email-validation" class="org-validation-message"></div>
</div>
<div id="email-validation"></div>
</div>
</th:block>
</body>
<th:block>
<script th:fragment="commonUserInfoScript">
<script th:fragment="commonUserInfoScript" th:inline="javascript">
// 이메일 조합 함수
function combineEmail() {
let emailId = document.getElementById('userId').value;
let domain = document.getElementById('domain').value;
let emailId = $('#userId').val();
let domain = $('#domain').val();
let combined = emailId + '@' + domain;
let loginIdInput = document.getElementById('loginId');
if (loginIdInput) {
loginIdInput.value = combined;
}
$('#loginId').val(combined);
}
function handleEmailValidationResponse(response) {
const individualConversionForm = document.getElementById('individualConversionForm');
const emailChangeForm = document.getElementById('emailChangeForm');
const newUserForm = document.getElementById('newUserForm');
const userIdInput = document.getElementById('userId');
const domainInput = document.getElementById('domain');
const checkButton = document.querySelector('.btn_check_email');
const loginIdInput = document.getElementById('loginId');
const scenarioInput = document.getElementById('registrationScenario');
const individualConversionForm = $('#individualConversionForm');
const emailChangeForm = $('#emailChangeForm');
const newUserForm = $('#newUserForm, #newUserFields');
const userIdInput = $('#userId');
const domainInput = $('#domain');
const checkButton = $('.btn_check_email');
const loginIdInput = $('#loginId');
const scenarioInput = $('#registrationScenario');
if (response && response.showPopup) {
switch (response.popupType) {
case "validEmail":
case "newUser":
// 시나리오 값 설정
if (scenarioInput) {
scenarioInput.value = 'new';
// hidden input이 form의 일부인지 확인
if (!scenarioInput.form) {
document.getElementById('registerForm').appendChild(scenarioInput);
}
}
scenarioInput.val('new');
customPopups.showAlert(response.message);
if (checkButton) {
checkButton.style.display = 'none';
}
checkButton.hide();
if (userIdInput && domainInput && loginIdInput) {
userIdInput.disabled = true;
domainInput.disabled = true;
userIdInput.style.backgroundColor = '#e0e0e0';
domainInput.style.backgroundColor = '#e0e0e0';
userIdInput.prop('disabled', true);
domainInput.prop('disabled', true);
userIdInput.css('backgroundColor', '#e0e0e0');
domainInput.css('backgroundColor', '#e0e0e0');
const combinedEmail = `${userIdInput.value.trim()}@${domainInput.value.trim()}`;
loginIdInput.value = combinedEmail;
}
const combinedEmail = `${userIdInput.val().trim()}@${domainInput.val().trim()}`;
loginIdInput.val(combinedEmail);
if (newUserForm) newUserForm.style.display = 'block';
if (individualConversionForm) individualConversionForm.style.display = 'none';
if (emailChangeForm) emailChangeForm.style.display = 'none';
newUserForm.show();
individualConversionForm.hide();
emailChangeForm.hide();
break;
case "conversionOrChange":
@@ -104,51 +85,33 @@
response.message,
function () {
// "이메일 유지" 선택 시 동작
if (scenarioInput) {
scenarioInput.value = 'retain';
if (!scenarioInput.form) {
document.getElementById('registerForm').appendChild(scenarioInput);
}
}
scenarioInput.val('retain');
if (individualConversionForm) individualConversionForm.style.display = 'block';
if (emailChangeForm) emailChangeForm.style.display = 'none';
if (newUserForm) newUserForm.style.display = 'none';
individualConversionForm.show();
emailChangeForm.hide();
newUserForm.hide();
if (userIdInput && domainInput) {
userIdInput.disabled = true;
domainInput.disabled = true;
userIdInput.style.backgroundColor = '#e0e0e0';
domainInput.style.backgroundColor = '#e0e0e0';
}
userIdInput.prop('disabled', true);
domainInput.prop('disabled', true);
userIdInput.css('backgroundColor', '#e0e0e0');
domainInput.css('backgroundColor', '#e0e0e0');
if (checkButton) {
checkButton.style.display = 'none';
}
checkButton.hide();
},
function () {
// "이메일 전환" 선택 시 동작
if (scenarioInput) {
scenarioInput.value = 'change';
if (!scenarioInput.form) {
document.getElementById('registerForm').appendChild(scenarioInput);
}
}
scenarioInput.val('change');
if (emailChangeForm) emailChangeForm.style.display = 'block';
if (individualConversionForm) individualConversionForm.style.display = 'none';
if (newUserForm) newUserForm.style.display = 'none';
emailChangeForm.show();
individualConversionForm.hide();
newUserForm.hide();
if (userIdInput && domainInput) {
userIdInput.disabled = true;
domainInput.disabled = true;
userIdInput.style.backgroundColor = '#e0e0e0';
domainInput.style.backgroundColor = '#e0e0e0';
}
userIdInput.prop('disabled', true);
domainInput.prop('disabled', true);
userIdInput.css('backgroundColor', '#e0e0e0');
domainInput.css('backgroundColor', '#e0e0e0');
if (checkButton) {
checkButton.style.display = 'none';
}
checkButton.hide();
}
);
break;
@@ -169,11 +132,35 @@
}
// 이메일 입력 필드에 이벤트 리스너 추가
document.addEventListener('DOMContentLoaded', function () {
let userIdInput = document.getElementById("userId");
let domainInput = document.getElementById("domain");
if (userIdInput) userIdInput.addEventListener("input", combineEmail);
if (domainInput) domainInput.addEventListener("input", combineEmail);
$(document).ready(function () {
$('#userId, #domain').on('input', combineEmail);
// 이메일 중복 체크 버튼 클릭
$('.btn_check_email').on('click', function () {
const loginId = $('#loginId').val();
const registrationType = $('input[name="registrationType"]').val();
if (!loginId || loginId.indexOf('@') === -1) {
customPopups.showAlert('이메일 주소를 올바르게 입력해주세요.');
return;
}
$.ajax({
url: /*[[@{/check_id}]]*/ '/check_id',
type: 'POST',
data: {
loginId: loginId,
registrationType: registrationType,
_csrf: $('input[name="_csrf"]').val()
},
success: function (response) {
handleEmailValidationResponse(response);
},
error: function () {
customPopups.showAlert('처리 중 오류가 발생했습니다. 다시 시도해주세요.');
}
});
});
});
</script>
@@ -5,127 +5,94 @@
<th:block th:fragment="newUserForm">
<div id="newUserFields">
<!-- 성명 -->
<div class="info1 pt28">
<p class="title">
<span class="dot">성명</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" th:field="*{userName}" id="userName" class="common_input_type_1"
th:placeholder="#{portalUser.Register.userName}">
</div>
<div class="btn_check pc-only"></div>
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
성명
</label>
<div class="org-form-input-wrapper">
<input type="text" th:field="*{userName}" id="userName" class="org-form-input"
th:placeholder="#{portalUser.Register.userName}">
</div>
</div>
<!-- 휴대폰 번호 입력 -->
<div class="info1 pt28">
<p class="title">
<span class="dot">휴대폰 번호</span>
</p>
<div class="info_line">
<div class="info_box1">
<span class="input">
<div class="custom_select ct_sc h_inp2" id="phoneMobile">
<div class="select_selected">선택</div>
<ul class="common_selecttype select_items select_hide ct_sc2">
<li>010</li>
<li>011</li>
<li>017</li>
<li>016</li>
<li>018</li>
</ul>
</div>
</span>
<span class="dash">-</span>
<span class="input">
<input type="text" id="cellPhone1" maxlength="4" class="common_input_type_1 w_inp3"
th:placeholder="#{portal.Register.phone1}">
</span>
<span class="dash">-</span>
<span class="input">
<input type="text" id="cellPhone2" maxlength="4" class="common_input_type_1 w_inp3"
th:placeholder="#{portal.Register.phone2}">
</span>
<input type="hidden" th:field="*{mobileNumber}" id="mobileNumber">
</div>
<div class="btn_check">
<a class="common_btn_type_2 cbt btn_auth"
th:hx-post="@{/auth/request_auth_number}"
hx-include="#csrf, #mobileNumber"
hx-target="#authNumberContainer"
hx-swap="none">
<span>인증번호 받기</span>
</a>
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
휴대폰 번호
</label>
<div class="org-form-input-wrapper">
<div class="org-compound-input">
<select class="org-form-select phone-number" id="phoneMobile">
<option value="">선택</option>
<option value="010">010</option>
<option value="011">011</option>
<option value="017">017</option>
<option value="016">016</option>
<option value="018">018</option>
</select>
<span class="separator">-</span>
<input type="text" id="cellPhone1" maxlength="4" class="org-form-input phone-number"
th:placeholder="#{portal.Register.phone1}">
<span class="separator">-</span>
<input type="text" id="cellPhone2" maxlength="4" class="org-form-input phone-number"
th:placeholder="#{portal.Register.phone2}">
</div>
<input type="hidden" th:field="*{mobileNumber}" id="mobileNumber">
<button type="button" class="org-btn org-btn-check btn_auth">
인증번호 받기
</button>
<div id="mobile-validation" class="org-validation-message"></div>
</div>
</div>
<div id="mobile-validation"></div>
<!-- 인증번호 입력 -->
<div class="info1 pt28" id="authNumberContainer">
<p class="title">
<span class="dot">인증번호 입력</span>
</p>
<div class="info_line">
<div class="info_box1">
<div class="certify_box">
<input type="text" th:field="*{authNumber}" id="authNumber" maxlength="6" pattern="[0-9]*"
inputmode="numeric" class="common_input_type_1 w_input" th:placeholder="#{ncrdRegister.validate.authNumber}" 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="#authNumber-validation"
hx-swap="none">
<span>인증번호 확인</span>
</a>
<div class="org-form-group" id="authNumberContainer" style="display: none;">
<label class="org-form-label">
<span class="required">*</span>
인증번호 입력
</label>
<div class="org-form-input-wrapper">
<div class="org-auth-input-group">
<input type="text" th:field="*{authNumber}" id="authNumber" maxlength="6" pattern="[0-9]*"
inputmode="numeric" class="org-form-input" th:placeholder="#{ncrdRegister.validate.authNumber}" required>
<span class="org-timer" id="certify_time">03:00</span>
</div>
<button type="button" class="org-btn org-btn-check btn_verify_auth">
인증번호 확인
</button>
<div id="authNumber-validation" class="org-validation-message"></div>
</div>
</div>
<div id="authNumber-validation"></div>
<div class="info1 pt28">
<p class="title">
<span class="dot">비밀번호</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="password" name="password" id="password" class="common_input_type_1 w_input"
th:placeholder="#{portalUser.Register.pass}"
th:hx-post="@{/check_password}"
hx-trigger="blur"
hx-include="#mobileNumber, #loginId"
hx-target="#password-validation"
hx-swap="innerHTML">
<input type="hidden" name="isPasswordValid" id="isPasswordValid"/>
</div>
<div class="btn_check pc-only"></div>
<!-- 비밀번호 -->
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
비밀번호
</label>
<div class="org-form-input-wrapper">
<input type="password" name="password" id="password" class="org-form-input"
th:placeholder="#{portalUser.Register.pass}">
<input type="hidden" name="isPasswordValid" id="isPasswordValid"/>
<div id="password-validation" class="org-validation-message"></div>
</div>
</div>
<div id="password-validation" class="validation-message"></div>
<!-- 비밀번호 확인 -->
<div class="info1 pt28">
<p class="title">
<span class="dot">비밀번호 확인</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="password" name="password2" id="password2" class="common_input_type_1 w_input"
th:placeholder="#{portalUser.Register.passConfirm}"
th:hx-post="@{/check_password_match}"
hx-trigger="blur"
hx-target="#password-match-validation"
hx-include="#password"
hx-swap="innerHTML">
<input type="hidden" name="isPasswordMatch" id="isPasswordMatch"/>
</div>
<div class="btn_check pc-only"></div>
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
비밀번호 확인
</label>
<div class="org-form-input-wrapper">
<input type="password" name="password2" id="password2" class="org-form-input"
th:placeholder="#{portalUser.Register.passConfirm}">
<input type="hidden" name="isPasswordMatch" id="isPasswordMatch"/>
<div id="password-match-validation" class="org-validation-message"></div>
</div>
</div>
<div id="password-match-validation" class="validation-message"></div>
</div>
</th:block>
@@ -134,16 +101,16 @@
<script th:fragment="newUserScript">
// 휴대폰 번호 조합 함수
function combineMobileNumber() {
let mobileElement = document.querySelector('#phoneMobile .select_selected');
if (!mobileElement) {
console.error('Mobile element not found');
let mobileSelect = document.getElementById('phoneMobile');
if (!mobileSelect) {
console.error('Mobile select element not found');
return null;
}
let prefix = mobileElement.textContent.trim();
let prefix = mobileSelect.value;
let middle = document.getElementById('cellPhone1').value.trim();
let last = document.getElementById('cellPhone2').value.trim();
if (prefix === '선택' || !middle || !last) {
if (!prefix || !middle || !last) {
return null;
}
@@ -159,7 +126,6 @@
}
document.addEventListener('DOMContentLoaded', function () {
activateSelect();
let countdownInterval;
let isAuthVerified = false;
@@ -182,11 +148,9 @@
combineMobileNumber();
});
// select_selected 변경 감지
$('#phoneMobile .select_items li').on('click', function () {
setTimeout(() => {
combineMobileNumber();
}, 100);
// select 변경 감지
$('#phoneMobile').on('change', function () {
combineMobileNumber();
});
// 인증번호 입력 제한
@@ -215,8 +179,10 @@
function handleAuthNumberRequest(response) {
if (response.valid) {
let authButton = document.querySelector(".btn_auth");
let authContainer = document.getElementById("authNumberContainer");
if (authButton) authButton.disabled = true;
if (authContainer) authContainer.style.display = 'flex';
startAuthNumberTimer();
customPopups.showAlert(response.message || "인증번호가 발송되었습니다.");
} else {
@@ -256,7 +222,8 @@
}
// 이벤트 리스너 등록
document.querySelectorAll('#phoneMobile .select_selected, #cellPhone1, #cellPhone2')
document.getElementById('phoneMobile').addEventListener('change', combineMobileNumber);
document.querySelectorAll('#cellPhone1, #cellPhone2')
.forEach(el => el.addEventListener('input', combineMobileNumber));
let authNumberInput = document.getElementById("authNumber");
@@ -275,76 +242,107 @@
}
// 인증번호 요청 버튼 이벤트 리스너
let authButton = document.querySelector(".btn_auth");
if (authButton) {
authButton.addEventListener("click", function (event) {
let mobileNumber = combineMobileNumber();
if (!mobileNumber) {
event.preventDefault(); // htmx 요청 중지
customPopups.showAlert("휴대폰 번호를 올바르게 입력해주세요.");
return;
}
document.getElementById('mobileNumber').value = mobileNumber;
// htmx 요청이 자동으로 실행됨
});
}
// 인증번호 확인 버튼 이벤트 리스너
let verifyAuthButton = document.querySelector(".btn_verify_auth");
if (verifyAuthButton) {
verifyAuthButton.addEventListener("click", function () {
let authNumber = document.getElementById('authNumber').value;
if (authNumber.length !== 6) {
customPopups.showAlert("6자리 인증번호를 입력해주세요.");
return;
}
});
}
// HTMX 이벤트 리스너
document.body.addEventListener('htmx:afterSwap', function (event) {
let targetId = event.detail.target ? event.detail.target.id : null;
if (targetId && ['password-validation', 'password-match-validation', 'authNumberContainer', 'authNumber-validation', 'email-validation'].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 'email-validation':
// handleEmailValidationResponse(response);
break;
case 'password-validation':
case 'password-match-validation':
let targetElement = document.getElementById(targetId);
targetElement.className = 'validation-message ' + (response.valid ? 'success' : 'error');
targetElement.textContent = response.message;
// 유효성 상태 저장
if (targetId === 'password-validation') {
document.getElementById('isPasswordValid').value = response.valid;
} else if (targetId === 'password-match-validation') {
document.getElementById('isPasswordMatch').value = response.valid;
}
break;
case 'authNumberContainer':
handleAuthNumberRequest(response);
break;
case 'authNumber-validation':
handleAuthNumberVerification(response);
break;
}
$('.btn_auth').on('click', function () {
let mobileNumber = combineMobileNumber();
if (!mobileNumber) {
customPopups.showAlert("휴대폰 번호를 올바르게 입력해주세요.");
return;
}
$('#mobileNumber').val(mobileNumber);
$.ajax({
url: /*[[@{/auth/request_auth_number}]]*/ '/auth/request_auth_number',
type: 'POST',
data: {
mobileNumber: mobileNumber,
_csrf: $('input[name="_csrf"]').val()
},
success: function (response) {
handleAuthNumberRequest(response);
},
error: function () {
customPopups.showAlert("처리 중 오류가 발생했습니다. 다시 시도해주세요.");
}
});
});
document.body.addEventListener('htmx:responseError', function (event) {
customPopups.showAlert("처리 중 오류가 발생했습니다. 다시 시도해주세요.");
// 인증번호 확인 버튼 이벤트 리스너
$('.btn_verify_auth').on('click', function () {
let authNumber = $('#authNumber').val();
if (authNumber.length !== 6) {
customPopups.showAlert("6자리 인증번호를 입력해주세요.");
return;
}
$.ajax({
url: /*[[@{/auth/verify_auth_number}]]*/ '/auth/verify_auth_number',
type: 'POST',
data: {
mobileNumber: $('#mobileNumber').val(),
authNumber: authNumber,
_csrf: $('input[name="_csrf"]').val()
},
success: function (response) {
handleAuthNumberVerification(response);
},
error: function () {
customPopups.showAlert("처리 중 오류가 발생했습니다. 다시 시도해주세요.");
}
});
});
// 비밀번호 검증
$('#password').on('blur', function () {
let password = $(this).val();
if (!password) return;
$.ajax({
url: /*[[@{/check_password}]]*/ '/check_password',
type: 'POST',
data: {
password: password,
mobileNumber: $('#mobileNumber').val(),
loginId: $('#loginId').val(),
_csrf: $('input[name="_csrf"]').val()
},
success: function (response) {
let targetElement = $('#password-validation');
targetElement.removeClass('success error');
targetElement.addClass(response.valid ? 'success' : 'error');
targetElement.text(response.message);
$('#isPasswordValid').val(response.valid);
},
error: function () {
customPopups.showAlert("처리 중 오류가 발생했습니다. 다시 시도해주세요.");
}
});
});
// 비밀번호 확인 검증
$('#password2').on('blur', function () {
let password2 = $(this).val();
if (!password2) return;
$.ajax({
url: /*[[@{/check_password_match}]]*/ '/check_password_match',
type: 'POST',
data: {
password: $('#password').val(),
password2: password2,
_csrf: $('input[name="_csrf"]').val()
},
success: function (response) {
let targetElement = $('#password-match-validation');
targetElement.removeClass('success error');
targetElement.addClass(response.valid ? 'success' : 'error');
targetElement.text(response.message);
$('#isPasswordMatch').val(response.valid);
},
error: function () {
customPopups.showAlert("처리 중 오류가 발생했습니다. 다시 시도해주세요.");
}
});
});
});
</script>
@@ -1,287 +1,274 @@
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:hx-on="http://www.w3.org/1999/xhtml">
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<body>
<th:block th:fragment="orgInfoForm">
<div class="form_type">
<div class="info1">
<p class="title">
<span class="dot">사업자등록번호</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" class="common_input_type_1 w_inp5 business-number" id="compRegNo1" maxlength="3"
th:placeholder="#{portalOrg.Register.compRegNo1}" required>
<span class="dash">-</span>
<input type="text" class="common_input_type_1 w_inp6 business-number" id="compRegNo2" maxlength="2"
th:placeholder="#{portalOrg.Register.compRegNo2}" required>
<span class="dash">-</span>
<input type="text" class="common_input_type_1 w_inp7 business-number" id="compRegNo3" maxlength="5"
th:placeholder="#{portalOrg.Register.compRegNo3}" required>
</div>
<div class="btn_check pc-only"></div>
<input type="hidden" id="compRegNo" th:field="*{compRegNo}" hx-trigger="change delay:1s"/>
<!-- Business Registration Number -->
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
사업자등록번호
</label>
<div class="org-form-input-wrapper">
<div class="org-compound-input">
<input type="text" class="org-form-input business-number" id="compRegNo1" maxlength="3"
placeholder="000" required>
<span class="separator">-</span>
<input type="text" class="org-form-input business-number" id="compRegNo2" maxlength="2"
placeholder="00" required>
<span class="separator">-</span>
<input type="text" class="org-form-input business-number" id="compRegNo3" maxlength="5"
placeholder="00000" required>
</div>
<input type="hidden" id="compRegNo" th:field="*{compRegNo}"/>
<div id="compRegNo-validation" class="org-validation-message"></div>
</div>
</div>
<div class="info1 pt28">
<p class="title">
<span class="dot">법인등록번호</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" class="common_input_type_1 w_inp8 corp-reg-no" id="corpRegNo1" maxlength="6"
th:placeholder="#{portalOrg.Register.corpRegNo1}" required>
<span class="dash">-</span>
<input type="text" class="common_input_type_1 w_inp9 corp-reg-no" id="corpRegNo2" maxlength="7"
th:placeholder="#{portalOrg.Register.corpRegNo2}" required>
</div>
<div class="btn_check pc-only"></div>
<input type="hidden" id="corpRegNo" th:field="*{corpRegNo}" hx-trigger="change delay:1s"/>
<!-- Corporate Registration Number -->
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
법인등록번호
</label>
<div class="org-form-input-wrapper">
<div class="org-compound-input">
<input type="text" class="org-form-input corp-reg-no" id="corpRegNo1" maxlength="6"
placeholder="000000" required>
<span class="separator">-</span>
<input type="text" class="org-form-input corp-reg-no" id="corpRegNo2" maxlength="7"
placeholder="0000000" required>
</div>
<input type="hidden" id="corpRegNo" th:field="*{corpRegNo}"/>
<div id="corpRegNo-validation" class="org-validation-message"></div>
</div>
</div>
<div class="info1 pt28">
<p class="title">
<span class="dot">법인명</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" class="common_input_type_1" id="orgName" th:field="*{orgName}"
th:placeholder="#{portalOrg.Register.orgName}"
th:classappend="${#fields.hasErrors('orgName')}? 'is-invalid'" required>
</div>
<div class="btn_check pc-only"></div>
<div th:if="${#fields.hasErrors('orgName')}" class="invalid-feedback">
<span th:errors="*{orgName}"></span>
</div>
<!-- Company Name -->
<div class="org-form-group">
<label class="org-form-label" for="orgName">
<span class="required">*</span>
법인명
</label>
<div class="org-form-input-wrapper">
<input type="text" class="org-form-input" id="orgName" th:field="*{orgName}"
placeholder="법인명을 입력하세요"
th:classappend="${#fields.hasErrors('orgName')}? 'is-invalid'" required>
<div th:if="${#fields.hasErrors('orgName')}" class="org-validation-message error">
<span th:errors="*{orgName}"></span>
</div>
</div>
</div>
<div class="info1 pt28">
<p class="title">
<span class="dot">대표자 성명</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" class="common_input_type_1" id="ceoName" th:field="*{ceoName}"
th:placeholder="#{portalOrg.Register.ceoName}"
th:classappend="${#fields.hasErrors('ceoName')}? 'is-invalid'" required>
</div>
<div class="btn_check pc-only"></div>
<div th:if="${#fields.hasErrors('ceoName')}" class="invalid-feedback">
<span th:errors="*{ceoName}"></span>
</div>
<!-- CEO Name -->
<div class="org-form-group">
<label class="org-form-label" for="ceoName">
<span class="required">*</span>
대표자 성명
</label>
<div class="org-form-input-wrapper">
<input type="text" class="org-form-input" id="ceoName" th:field="*{ceoName}"
placeholder="대표자 성명을 입력하세요"
th:classappend="${#fields.hasErrors('ceoName')}? 'is-invalid'" required>
<div th:if="${#fields.hasErrors('ceoName')}" class="org-validation-message error">
<span th:errors="*{ceoName}"></span>
</div>
</div>
</div>
<div class="info1 pt28">
<p class="title">
<span class="dot">사업장 소재지</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" class="common_input_type_1" id="orgAddr" th:field="*{orgAddr}"
th:placeholder="#{portalOrg.Register.orgAddr}"
th:classappend="${#fields.hasErrors('orgAddr')}? 'is-invalid'" required>
</div>
<div class="btn_check pc-only"></div>
<div th:if="${#fields.hasErrors('orgAddr')}" class="invalid-feedback">
<span th:errors="*{orgAddr}"></span>
</div>
<!-- Business Address -->
<div class="org-form-group">
<label class="org-form-label" for="orgAddr">
<span class="required">*</span>
사업장 소재지
</label>
<div class="org-form-input-wrapper">
<input type="text" class="org-form-input" id="orgAddr" th:field="*{orgAddr}"
placeholder="사업장 소재지를 입력하세요"
th:classappend="${#fields.hasErrors('orgAddr')}? 'is-invalid'" required>
<div th:if="${#fields.hasErrors('orgAddr')}" class="org-validation-message error">
<span th:errors="*{orgAddr}"></span>
</div>
</div>
</div>
<div class="info1 pt28">
<p class="title">
<span class="dot">업태</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" class="common_input_type_1 w_input" id="orgSectors" th:field="*{orgSectors}"
th:placeholder="#{portalOrg.Register.orgSectors}">
</div>
<div class="btn_check pc-only"></div>
</div>
<!-- Business Type -->
<div class="org-form-group">
<label class="org-form-label" for="orgSectors">
<span class="required">*</span>
업태
</label>
<div class="org-form-input-wrapper">
<input type="text" class="org-form-input" id="orgSectors" th:field="*{orgSectors}"
placeholder="업태를 입력하세요" required>
</div>
</div>
<div class="info1 pt28">
<p class="title">
<span class="dot">업종</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" class="common_input_type_1" id="orgIndustryType" th:field="*{orgIndustryType}"
th:placeholder="#{portalOrg.Register.orgIndustryType}">
</div>
<div class="btn_check pc-only"></div>
</div>
<!-- Industry Type -->
<div class="org-form-group">
<label class="org-form-label" for="orgIndustryType">
<span class="required">*</span>
업종
</label>
<div class="org-form-input-wrapper">
<input type="text" class="org-form-input" id="orgIndustryType" th:field="*{orgIndustryType}"
placeholder="업종을 입력하세요" required>
</div>
</div>
<div class="info1 pt28">
<p class="title">
<span class="dot">회사 전화번호</span>
</p>
<div class="info_line">
<div class="info_box1">
<span class="input">
<div class="custom_select ct_sc h_inp2" id="orgAreaCode">
<div class="select_selected active">선택</div>
<ul class="common_selecttype select_hide select_items ct_sc2">
<li>국번없음</li>
<li>02</li>
<li>031</li>
<li>032</li>
<li>033</li>
<li>041</li>
<li>042</li>
<li>043</li>
<li>044</li>
<li>051</li>
<li>052</li>
<li>053</li>
<li>054</li>
<li>055</li>
<li>061</li>
<li>062</li>
<li>063</li>
<li>064</li>
<li>060</li>
<li>070</li>
<li>080</li>
<li>010</li>
<li>011</li>
<li>016</li>
<li>017</li>
<li>018</li>
<li>019</li>
</ul>
</div>
</span>
<span class="dash">-</span>
<span class="input">
<input type="text" id="orgPhoneNumber1" class="common_input_type_1 w_inp3" maxlength="4"
th:placeholder="#{portal.Register.phone1}">
</span>
<span class="dash">-</span>
<span class="input">
<input type="text" id="orgPhoneNumber2" class="common_input_type_1 w_inp3" maxlength="4"
th:placeholder="#{portal.Register.phone2}">
</span>
</div>
<input type="hidden" id="orgPhoneNumber" th:field="*{orgPhoneNumber}">
<div class="btn_check pc-only"></div>
<!-- Company Phone Number -->
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
회사 전화번호
</label>
<div class="org-form-input-wrapper">
<div class="org-compound-input">
<select class="org-form-select" id="orgAreaCode" required>
<option value="">선택</option>
<option value="국번없음">국번없음</option>
<option value="02">02</option>
<option value="031">031</option>
<option value="032">032</option>
<option value="033">033</option>
<option value="041">041</option>
<option value="042">042</option>
<option value="043">043</option>
<option value="044">044</option>
<option value="051">051</option>
<option value="052">052</option>
<option value="053">053</option>
<option value="054">054</option>
<option value="055">055</option>
<option value="061">061</option>
<option value="062">062</option>
<option value="063">063</option>
<option value="064">064</option>
<option value="070">070</option>
<option value="080">080</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>
<option value="019">019</option>
</select>
<span class="separator">-</span>
<input type="text" id="orgPhoneNumber1" class="org-form-input" maxlength="4"
placeholder="0000">
<span class="separator">-</span>
<input type="text" id="orgPhoneNumber2" class="org-form-input" maxlength="4"
placeholder="0000">
</div>
<input type="hidden" id="orgPhoneNumber" th:field="*{orgPhoneNumber}">
</div>
</div>
<!-- Service Name -->
<div class="org-form-group">
<label class="org-form-label" for="serviceName">
<span class="required">*</span>
서비스명
</label>
<div class="org-form-input-wrapper">
<input type="text" class="org-form-input" id="serviceName" th:field="*{serviceName}"
placeholder="서비스명을 입력하세요"
th:classappend="${#fields.hasErrors('serviceName')}? 'is-invalid'" required>
<div th:if="${#fields.hasErrors('serviceName')}" class="org-validation-message error">
<span th:errors="*{serviceName}"></span>
</div>
</div>
<div class="info1 pt28">
<p class="title">
<span class="dot">서비스명</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" class="common_input_type_1" id="serviceName" th:field="*{serviceName}"
th:placeholder="#{portalOrg.Register.serviceName}"
th:classappend="${#fields.hasErrors('serviceName')}? 'is-invalid'" required>
</div>
<div class="btn_check pc-only"></div>
<div th:if="${#fields.hasErrors('serviceName')}" class="invalid-feedback">
<span th:errors="*{serviceName}"></span>
</div>
</div>
<!-- Customer Service Phone Number -->
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
고객센터 전화번호
</label>
<div class="org-form-input-wrapper">
<div class="org-compound-input">
<select class="org-form-select" id="scAreaCode" required>
<option value="">선택</option>
<option value="국번없음">국번없음</option>
<option value="02">02</option>
<option value="031">031</option>
<option value="032">032</option>
<option value="033">033</option>
<option value="041">041</option>
<option value="042">042</option>
<option value="043">043</option>
<option value="044">044</option>
<option value="051">051</option>
<option value="052">052</option>
<option value="053">053</option>
<option value="054">054</option>
<option value="055">055</option>
<option value="061">061</option>
<option value="062">062</option>
<option value="063">063</option>
<option value="064">064</option>
<option value="070">070</option>
<option value="080">080</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>
<option value="019">019</option>
</select>
<span class="separator">-</span>
<input type="text" id="scPhoneNumber1" class="org-form-input" maxlength="4"
placeholder="0000">
<span class="separator">-</span>
<input type="text" id="scPhoneNumber2" class="org-form-input" maxlength="4"
placeholder="0000">
</div>
<input type="hidden" id="scPhoneNumber" th:field="*{scPhoneNumber}">
</div>
<div class="info1 pt28">
<p class="title">
<span class="dot">고객센터 전화번호</span>
</p>
<div class="info_line">
<div class="info_box1">
<span class="input">
<div class="custom_select ct_sc h_inp2" id="scAreaCode">
<div class="select_selected active">선택</div>
<ul class="common_selecttype select_hide select_items ct_sc2">
<li>국번없음</li>
<li>02</li>
<li>031</li>
<li>032</li>
<li>033</li>
<li>041</li>
<li>042</li>
<li>043</li>
<li>044</li>
<li>051</li>
<li>052</li>
<li>053</li>
<li>054</li>
<li>055</li>
<li>061</li>
<li>062</li>
<li>063</li>
<li>064</li>
<li>060</li>
<li>070</li>
<li>080</li>
<li>010</li>
<li>011</li>
<li>016</li>
<li>017</li>
<li>018</li>
<li>019</li>
</ul>
</div>
</span>
<span class="dash">-</span>
<span class="input">
<input type="text" id="scPhoneNumber1" class="common_input_type_1 w_inp3" maxlength="4"
th:placeholder="#{portal.Register.phone1}">
</span>
<span class="dash">-</span>
<span class="input">
<input type="text" id="scPhoneNumber2" class="common_input_type_1 w_inp3" maxlength="4"
th:placeholder="#{portal.Register.phone2}">
</span>
</div>
<input type="hidden" id="scPhoneNumber" th:field="*{scPhoneNumber}">
<div class="btn_check pc-only"></div>
</div>
<!-- Business Registration Certificate Upload -->
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
사업자 등록증
</label>
<div class="org-form-input-wrapper">
<div class="org-file-upload">
<input type="text" class="org-file-display" id="fileNameDisplay" readonly placeholder="첨부파일을 선택하세요">
<input type="hidden" id="compRegFile" th:field="*{compRegFile}">
<label class="org-file-label" for="files">
<i class="fas fa-upload"></i> 파일선택
<input type="file" id="files" name="files"
accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.hwp,.gif,.jpg,.png">
</label>
<button type="button" class="org-file-remove" id="fileRemoveBtn" style="display: none;">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="info1 pt28">
<p class="title tit_add w_tit">
<span class="dot">사업자 등록증</span>
</p>
<div class="info_line info_add">
<div class="info_box1 info_add2">
<div class="filebox">
<input type="text" class="upload-name before" readonly="readonly" placeholder="첨부파일">
<input type="hidden" id="compRegFile" th:field="*{compRegFile}">
</div>
<div class="btn_check">
<div class="file_btn">
<label for="files">파일선택</label>
<input type="file" id="files" name="files" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.hwp,.gif,.jpg,.png">
</div>
</div>
</div>
<div th:if="${#fields.hasErrors('compRegFile')}" class="invalid-feedback">
<span th:errors="*{compRegFile}"></span>
</div>
<div class="list-dot">
<p>첨부파일은 10MB 이내로 등록해 주세요.</p>
<p>문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)</p>
</div>
<div class="org-file-notice">
<p>첨부파일은 10MB 이내로 등록해 주세요.</p>
<p>문서파일과 이미지 파일만 등록 가능합니다. (pdf, doc, docx, xls, xlsx, ppt, pptx, hwp, gif, jpg, png)</p>
</div>
<div th:if="${#fields.hasErrors('compRegFile')}" class="org-validation-message error">
<span th:errors="*{compRegFile}"></span>
</div>
</div>
</div>
</th:block>
</body>
<th:block layout:fragment="contentScript">
<script th:fragment="orgInfoScript">
function validatePhoneNumber(prefix) {
const areaCode = document.getElementById(`${prefix}AreaCode`).value;
const part1 = document.getElementById(`${prefix}PhoneNumber1`).value.trim();
const part2 = document.getElementById(`${prefix}PhoneNumber2`).value.trim();
// 회사 전화번호 검증
if (!part1 || !part2) {
if (!areaCode || areaCode === '' || !part1 || !part2) {
customPopups.showAlert(`${prefix === 'org' ? '회사' : '고객센터'} 전화번호를 모두 입력해주세요.`);
return false;
}
@@ -289,7 +276,6 @@
}
document.addEventListener('DOMContentLoaded', function() {
activateSelect();
function checkAndSendData(parts, expectedLengths, hiddenInputId, url, paramName) {
let isValid = true;
@@ -305,26 +291,37 @@
if (isValid) {
document.getElementById(hiddenInputId).value = fullValue;
let values = {};
values[paramName] = fullValue;
// CSRF 토큰을 정확하게 가져오기
const csrfToken = document.querySelector('input[name="_csrf"]')?.value ||
document.querySelector('meta[name="_csrf"]')?.content;
document.querySelector('meta[name="_csrf"]')?.content;
htmx.ajax('POST', url, {
values: values,
// AJAX로 검증 요청
$.ajax({
url: url,
type: 'POST',
data: {
[paramName]: fullValue,
_csrf: csrfToken
},
headers: {
'X-XSRF-TOKEN': csrfToken,
'X-Custom-Handler': 'registration-validation'
},
swap: 'none',
target: '#' + hiddenInputId // 명시적 타겟 지정
}).then(function() {
// HTMX 이벤트가 제대로 발생하는지 확인
console.log('HTMX request completed');
}).catch(function(error) {
console.error('HTMX request failed:', error);
success: function(response) {
const validationDiv = document.getElementById(hiddenInputId + '-validation');
if (validationDiv) {
if (response.valid) {
validationDiv.className = 'org-validation-message success';
validationDiv.textContent = response.message || '유효한 번호입니다.';
} else {
validationDiv.className = 'org-validation-message error';
validationDiv.textContent = response.message || '유효하지 않은 번호입니다.';
}
}
},
error: function() {
console.error('Validation request failed');
}
});
}
}
@@ -346,10 +343,11 @@
checkAndSendData(parts, [6, 7], 'corpRegNo', '/check-corp-reg-no', 'corpRegNo');
}
// 파일 선택 관련 코드
// File upload handling
const fileInput = document.getElementById('files');
const fileNameInput = document.querySelector('.upload-name');
const fileNameDisplay = document.getElementById('fileNameDisplay');
const compRegFileInput = document.getElementById('compRegFile');
const fileRemoveBtn = document.getElementById('fileRemoveBtn');
fileInput.addEventListener('change', function() {
const files = fileInput.files;
@@ -378,22 +376,20 @@
return;
}
fileNameInput.value = fileNames.join(', ');
fileNameInput.classList.remove('before');
fileNameInput.classList.add('i_trash', 'after');
fileNameDisplay.value = fileNames.join(', ');
fileNameDisplay.classList.add('has-file');
compRegFileInput.value = fileNames.join(', '); // hidden input에 파일 이름들 설정
fileRemoveBtn.style.display = 'block';
}
});
// 파일명 입력란 클릭 시 파일 제거
fileNameInput.addEventListener('click', function() {
if (fileNameInput.classList.contains('i_trash')) {
fileInput.value = '';
fileNameInput.value = '';
compRegFileInput.value = ''; // hidden input 값도 제거
fileNameInput.classList.remove('i_trash', 'after');
fileNameInput.classList.add('before');
}
// File remove button
fileRemoveBtn.addEventListener('click', function() {
fileInput.value = '';
fileNameDisplay.value = '';
compRegFileInput.value = '';
fileNameDisplay.classList.remove('has-file');
fileRemoveBtn.style.display = 'none';
});
function addListeners() {
@@ -404,21 +400,15 @@
document.getElementById('corpRegNo1').addEventListener('input', checkCorporateNumber);
document.getElementById('corpRegNo2').addEventListener('input', checkCorporateNumber);
// 전화번호 조합 함수 추가
// 전화번호 조합 함수
function combinePhoneNumber(prefix) {
const areaCode = document.querySelector(`#${prefix}AreaCode .select_selected`).textContent;
const areaCode = document.getElementById(`${prefix}AreaCode`).value;
const part1 = document.getElementById(`${prefix}PhoneNumber1`).value;
const part2 = document.getElementById(`${prefix}PhoneNumber2`).value;
if (areaCode === '선택' || !part1 || !part2) return;
if (!areaCode || areaCode === '' || !part1 || !part2) return;
let fullNumber = '';
// 지역번호가 '없음'인 경우에도 접두어로 저장
if (areaCode === '국번없음') {
fullNumber = `국번없음-${part1}-${part2}`;
} else {
fullNumber = `${areaCode}-${part1}-${part2}`;
}
const fullNumber = `${areaCode}-${part1}-${part2}`;
// hidden input에 하이픈이 포함된 값 설정
const hiddenInput = document.getElementById(`${prefix}PhoneNumber`);
@@ -443,12 +433,11 @@
});
// 지역번호 선택 시에도 전화번호 조합
document.querySelector('#orgAreaCode').addEventListener('click', () => combinePhoneNumber('org'));
document.querySelector('#scAreaCode').addEventListener('click', () => combinePhoneNumber('sc'));
document.getElementById('orgAreaCode').addEventListener('change', () => combinePhoneNumber('org'));
document.getElementById('scAreaCode').addEventListener('change', () => combinePhoneNumber('sc'));
}
addListeners();
});
</script>
</th:block>
@@ -1,181 +1,218 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.w3.org/1999/xhtml">
<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="orgUserInfoForm">
<div class="info1 pt28">
<p class="title">
<span class="dot">이메일 아이디</span>
</p>
<div class="info_line">
<div class="info_box1">
<input type="text" id="userId" class="common_input_type_1 w_inp"
th:placeholder="#{portalUser.Register.email}">
<span class="special_t">@</span>
<input type="text" id="domain" class="common_input_type_1 w_inp2"
th:placeholder="#{portalUser.Register.domain}">
<input type="hidden" id="loginId" name="loginId" th:value="${loginId}">
<input type="hidden" name="registrationType" th:value="${registrationType}"/>
<input type="hidden" id="registrationScenario" name="registrationScenario" value="new"/>
</div>
<div class="btn_check">
<a class="common_btn_type_2 cbt btn_check_email"
th:hx-post="@{/check_id}"
hx-include="#csrf, #loginId, [name='registrationType']"
hx-target="#email-validation"
hx-swap="none">
<span>중복체크</span>
</a>
</div>
</div>
<div id="email-validation"></div>
</div>
<div id="dynamicFormContainer">
<div id="individualConversionForm" style="display: none;">
<div th:include="~{apps/register/components/individualConversionForm :: individualConversionForm}"></div>
</div>
<div id="emailChangeForm" style="display: none;">
<div th:include="~{apps/register/components/emailChangeForm :: emailChangeForm}"></div>
</div>
<div id="newUserForm" style="display: none;">
<div th:include="~{apps/register/components/newUserInfoForm :: newUserForm}"></div>
</div>
<!-- Email ID -->
<div class="org-form-group">
<label class="org-form-label">
<span class="required">*</span>
이메일 아이디
</label>
<div class="org-input-button-group">
<div class="org-compound-input" style="flex: 1;">
<input type="text" id="userId" class="org-form-input"
placeholder="이메일 아이디">
<span class="separator">@</span>
<input type="text" id="domain" class="org-form-input"
placeholder="도메인">
</div>
<button type="button" class="org-btn org-btn-check btn_check_email">
중복체크
</button>
</div>
<input type="hidden" id="loginId" name="loginId" th:value="${loginId}">
<input type="hidden" name="registrationType" th:value="${registrationType}"/>
<input type="hidden" id="registrationScenario" name="registrationScenario" value="new"/>
<div id="email-validation" class="org-validation-message"></div>
</div>
<!-- Dynamic Form Container -->
<div id="dynamicFormContainer">
<div id="individualConversionForm" style="display: none;">
<div th:include="~{apps/register/components/individualConversionForm :: individualConversionForm}"></div>
</div>
<div id="emailChangeForm" style="display: none;">
<div th:include="~{apps/register/components/emailChangeForm :: emailChangeForm}"></div>
</div>
<div id="newUserForm" style="display: none;">
<div th:include="~{apps/register/components/newUserInfoForm :: newUserForm}"></div>
</div>
</div>
</th:block>
</body>
<th:block layout:fragment="contentScript">
<script th:fragment="orgUserInfoScript">
// 이메일 조합 함수
function combineEmail() {
let emailId = document.getElementById('userId').value;
let domain = document.getElementById('domain').value;
let combined = emailId + '@' + domain;
let loginIdInput = document.getElementById('loginId');
if (loginIdInput) {
loginIdInput.value = combined;
<script th:fragment="orgUserInfoScript">
// 이메일 조합 함수
function combineEmail() {
let emailId = document.getElementById('userId').value;
let domain = document.getElementById('domain').value;
let combined = emailId + '@' + domain;
let loginIdInput = document.getElementById('loginId');
if (loginIdInput) {
loginIdInput.value = combined;
}
}
function handleEmailValidationResponse(response) {
const individualConversionForm = document.getElementById('individualConversionForm');
const emailChangeForm = document.getElementById('emailChangeForm');
const newUserForm = document.getElementById('newUserForm');
const userIdInput = document.getElementById('userId');
const domainInput = document.getElementById('domain');
const checkButton = document.querySelector('.btn_check_email');
const loginIdInput = document.getElementById('loginId');
const scenarioInput = document.getElementById('registrationScenario');
if (response && response.showPopup) {
switch (response.popupType) {
case "validEmail":
case "newUser":
// 시나리오 값 설정
if (scenarioInput) {
scenarioInput.value = 'new';
// hidden input이 form의 일부인지 확인
if (!scenarioInput.form) {
document.getElementById('registerForm').appendChild(scenarioInput);
}
}
}
function handleEmailValidationResponse(response) {
const individualConversionForm = document.getElementById('individualConversionForm');
const emailChangeForm = document.getElementById('emailChangeForm');
const newUserForm = document.getElementById('newUserForm');
const userIdInput = document.getElementById('userId');
const domainInput = document.getElementById('domain');
const checkButton = document.querySelector('.btn_check_email');
const loginIdInput = document.getElementById('loginId');
const scenarioInput = document.getElementById('registrationScenario');
customPopups.showAlert(response.message);
if (response && response.showPopup) {
switch (response.popupType) {
case "validEmail":
case "newUser":
// 시나리오 값 설정
if (scenarioInput) {
scenarioInput.value = 'new';
// hidden input이 form의 일부인지 확인
if (!scenarioInput.form) {
document.getElementById('registerForm').appendChild(scenarioInput);
}
}
if (checkButton) {
checkButton.style.display = 'none';
}
customPopups.showAlert(response.message);
if (userIdInput && domainInput && loginIdInput) {
userIdInput.disabled = true;
domainInput.disabled = true;
userIdInput.style.backgroundColor = '#e0e0e0';
domainInput.style.backgroundColor = '#e0e0e0';
if (checkButton) {
checkButton.style.display = 'none';
}
const combinedEmail = `${userIdInput.value.trim()}@${domainInput.value.trim()}`;
loginIdInput.value = combinedEmail;
}
if (userIdInput && domainInput && loginIdInput) {
userIdInput.disabled = true;
domainInput.disabled = true;
userIdInput.style.backgroundColor = '#e0e0e0';
domainInput.style.backgroundColor = '#e0e0e0';
if (newUserForm) newUserForm.style.display = 'block';
if (individualConversionForm) individualConversionForm.style.display = 'none';
if (emailChangeForm) emailChangeForm.style.display = 'none';
break;
const combinedEmail = `${userIdInput.value.trim()}@${domainInput.value.trim()}`;
loginIdInput.value = combinedEmail;
}
if (newUserForm) newUserForm.style.display = 'block';
if (individualConversionForm) individualConversionForm.style.display = 'none';
if (emailChangeForm) emailChangeForm.style.display = 'none';
break;
case "conversionOrChange":
customPopups.showEmailValidationPopup(
response.message,
function () {
// "이메일 유지" 선택 시 동작
if (scenarioInput) {
scenarioInput.value = 'retain';
if (!scenarioInput.form) {
document.getElementById('registerForm').appendChild(scenarioInput);
}
}
if (individualConversionForm) individualConversionForm.style.display = 'block';
if (emailChangeForm) emailChangeForm.style.display = 'none';
if (newUserForm) newUserForm.style.display = 'none';
if (userIdInput && domainInput) {
userIdInput.disabled = true;
domainInput.disabled = true;
userIdInput.style.backgroundColor = '#e0e0e0';
domainInput.style.backgroundColor = '#e0e0e0';
}
if (checkButton) {
checkButton.style.display = 'none';
}
},
function () {
// "이메일 전환" 선택 시 동작
if (scenarioInput) {
scenarioInput.value = 'change';
if (!scenarioInput.form) {
document.getElementById('registerForm').appendChild(scenarioInput);
}
}
if (emailChangeForm) emailChangeForm.style.display = 'block';
if (individualConversionForm) individualConversionForm.style.display = 'none';
if (newUserForm) newUserForm.style.display = 'none';
if (userIdInput && domainInput) {
userIdInput.disabled = true;
domainInput.disabled = true;
userIdInput.style.backgroundColor = '#e0e0e0';
domainInput.style.backgroundColor = '#e0e0e0';
}
if (checkButton) {
checkButton.style.display = 'none';
}
}
);
break;
case "error":
customPopups.showAlert(response.message);
break;
default:
console.error('알 수 없는 팝업 유형:', response.popupType);
break;
case "conversionOrChange":
customPopups.showEmailValidationPopup(
response.message,
function () {
// "이메일 유지" 선택 시 동작
if (scenarioInput) {
scenarioInput.value = 'retain';
if (!scenarioInput.form) {
document.getElementById('registerForm').appendChild(scenarioInput);
}
}
} else if (response && response.message) {
customPopups.showAlert(response.message);
} else {
customPopups.showAlert('알 수 없는 오류가 발생했습니다.');
}
if (individualConversionForm) individualConversionForm.style.display = 'block';
if (emailChangeForm) emailChangeForm.style.display = 'none';
if (newUserForm) newUserForm.style.display = 'none';
if (userIdInput && domainInput) {
userIdInput.disabled = true;
domainInput.disabled = true;
userIdInput.style.backgroundColor = '#e0e0e0';
domainInput.style.backgroundColor = '#e0e0e0';
}
if (checkButton) {
checkButton.style.display = 'none';
}
},
function () {
// "이메일 전환" 선택 시 동작
if (scenarioInput) {
scenarioInput.value = 'change';
if (!scenarioInput.form) {
document.getElementById('registerForm').appendChild(scenarioInput);
}
}
if (emailChangeForm) emailChangeForm.style.display = 'block';
if (individualConversionForm) individualConversionForm.style.display = 'none';
if (newUserForm) newUserForm.style.display = 'none';
if (userIdInput && domainInput) {
userIdInput.disabled = true;
domainInput.disabled = true;
userIdInput.style.backgroundColor = '#e0e0e0';
domainInput.style.backgroundColor = '#e0e0e0';
}
if (checkButton) {
checkButton.style.display = 'none';
}
}
);
break;
case "error":
customPopups.showAlert(response.message);
break;
default:
console.error('알 수 없는 팝업 유형:', response.popupType);
break;
}
} else if (response && response.message) {
customPopups.showAlert(response.message);
} else {
customPopups.showAlert('알 수 없는 오류가 발생했습니다.');
}
}
// 이메일 입력 필드에 이벤트 리스너 추가
document.addEventListener('DOMContentLoaded', function () {
let userIdInput = document.getElementById("userId");
let domainInput = document.getElementById("domain");
if (userIdInput) userIdInput.addEventListener("input", combineEmail);
if (domainInput) domainInput.addEventListener("input", combineEmail);
// Email check button click handler
document.addEventListener('DOMContentLoaded', function () {
let userIdInput = document.getElementById("userId");
let domainInput = document.getElementById("domain");
const checkButton = document.querySelector('.btn_check_email');
if (userIdInput) userIdInput.addEventListener("input", combineEmail);
if (domainInput) domainInput.addEventListener("input", combineEmail);
// Email check button with AJAX
if (checkButton) {
checkButton.addEventListener('click', function() {
const loginId = document.getElementById('loginId').value;
const registrationType = document.querySelector('input[name="registrationType"]').value;
if (!loginId || loginId.indexOf('@') === -1) {
customPopups.showAlert('이메일 주소를 입력해주세요.');
return;
}
const csrfToken = document.querySelector('input[name="_csrf"]')?.value;
// Show loading
document.getElementById('loadingOverlay')?.classList.add('active');
$.ajax({
url: '/check_id',
type: 'POST',
data: {
loginId: loginId,
registrationType: registrationType,
_csrf: csrfToken
},
success: function(response) {
document.getElementById('loadingOverlay')?.classList.remove('active');
handleEmailValidationResponse(response);
},
error: function() {
document.getElementById('loadingOverlay')?.classList.remove('active');
customPopups.showAlert('서버 오류가 발생했습니다. 다시 시도해주세요.');
}
});
});
}
});
</script>
</script>
</th:block>
</html>
</html>
@@ -1,501 +1,515 @@
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/kbank_base_layout}">
layout:decorate="~{layout/kjbank_signup_layout}">
<body>
<section layout:fragment="contentFragment" class="content">
<script th:src="@{/js/htmx.min.js}"></script>
<div class="content_wrap">
<div class="sub_title2">
<h2 class="title add">법인회원가입</h2>
</div>
<div class="inner i_cs h_inner2">
<!-- 약관동의 섹션 -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<input type="hidden" name="registrationType" th:value="${registrationType}"/>
<th:block
th:replace="~{apps/register/userAgreementContent :: agreementContent(${termsOfUse}, ${privacyCollect})}"></th:block>
<form name="portalOrg" id="registerForm" role="form" th:action="@{/signup/portalOrg}"
th:object="${portalOrg}" method="post">
<!-- 법인 기본 정보 -->
<div class="terms_box">
<p>법인 기본 정보</p>
<span class="dot">필수 입력</span>
</div>
<div class="form_type">
<th:block th:replace="~{apps/register/components/orgBasicInfoForm :: orgInfoForm}"></th:block>
</div>
<!-- 기본 관리자 정보 -->
<div class="terms_box">
<p>법인 관리자 정보</p>
<span class="dot">필수 입력</span>
</div>
<div class="top_info mtb12">
<ul>
<li>법인관리자는 API 신청 및 이용자 초대 등 모든 권한을 보유합니다.</li>
</ul>
</div>
<div class="form_type" th:if="${registrationType == 'corporate'}">
<th:block th:replace="~{apps/register/components/orgUserInfoForm :: orgUserInfoForm}"></th:block>
</div>
<!-- 가입 버튼 -->
<div class="form_type">
<div class="btn_application">
<a type="button" class="common_btn_type_1 gray" id="cancelButton"><span>취소</span></a>
<div class="btn_gap"></div>
<a type="button" class="common_btn_type_1 btn_register"><span>가입신청</span></a>
</div>
</div>
</form>
</div>
</div>
<section layout:fragment="title">
<div class="org-page-title-banner">
<img th:src="@{/img/user_register_title.png}" alt="법인회원가입" class="title-image">
<h1>법인회원가입</h1>
</div>
</section>
<section layout:fragment="contentFragment">
<div class="org-register-page">
<!-- Page Title Banner -->
<div class="org-register-container">
<!-- Hidden Fields -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<input type="hidden" name="registrationType" th:value="${registrationType}"/>
<!-- Alert Container -->
<div id="alertContainer"></div>
<!-- Agreement Section -->
<th:block th:replace="~{apps/register/userAgreementContent :: agreementContent(${termsOfUse}, ${privacyCollect})}"></th:block>
<form name="portalOrg" id="registerForm" role="form" th:action="@{/signup/portalOrg}"
th:object="${portalOrg}" method="post">
<!-- Corporate Basic Information Section -->
<div class="org-section-header org-section-header--agreement">
<h3>법인 기본 정보</h3>
<span class="required-badge">필수 입력</span>
</div>
<th:block th:replace="~{apps/register/components/orgBasicInfoForm :: orgInfoForm}"></th:block>
<!-- Corporate Admin Information Section -->
<div class="org-section-header" style="margin-top: 48px;">
<h3>법인 관리자 정보</h3>
<span class="required-badge">필수 입력</span>
</div>
<div class="org-info-notice">
<ul>
<li>법인관리자는 API 신청 및 이용자 초대 등 모든 권한을 보유합니다.</li>
</ul>
</div>
<div th:if="${registrationType == 'corporate'}">
<th:block th:replace="~{apps/register/components/orgUserInfoForm :: orgUserInfoForm}"></th:block>
</div>
<!-- Action Buttons -->
<div class="org-action-buttons">
<button type="button" class="org-btn org-btn-secondary" id="cancelButton">취소</button>
<button type="button" class="org-btn org-btn-primary btn_register">가입신청</button>
</div>
</form>
<!-- Loading Overlay -->
<div class="org-loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
</div>
</div>
</div>
</section>
<th:block layout:fragment="contentScript">
<script th:if="${error}" th:inline="javascript">
$(document).ready(function () {
customPopups.showAlert([[${error}]]);
<script th:if="${error}" th:inline="javascript">
$(document).ready(function () {
customPopups.showAlert([[${error}]]);
});
</script>
<script th:replace="~{apps/register/userAgreementContent :: agreementScript}"></script>
<script th:replace="~{apps/register/components/orgBasicInfoForm :: orgInfoScript}"></script>
<script th:replace="~{apps/register/components/orgUserInfoForm :: orgUserInfoScript}"></script>
<script th:replace="~{apps/register/components/emailChangeForm :: emailChangeScript}"></script>
<script th:replace="~{apps/register/components/newUserInfoForm :: newUserScript}"></script>
<script th:inline="javascript">
document.addEventListener('DOMContentLoaded', function () {
let form = document.getElementById('registerForm');
let agreementForm = document.getElementById('agreementForm');
let btn_register = document.querySelector('.btn_register');
let isAuthVerified = false;
// registrationScenario를 hidden input에서 가져오도록 수정
let registrationScenario = document.getElementById('registrationScenario')?.value || 'new';
// hidden input의 값이 변경되면 registrationScenario 변수도 업데이트
const scenarioInput = document.getElementById('registrationScenario');
if (scenarioInput) {
scenarioInput.addEventListener('change', function () {
registrationScenario = this.value;
console.log('Registration Scenario Updated:', registrationScenario);
});
</script>
<script th:replace="~{apps/register/userAgreementContent :: agreementScript}"></script>
<script th:replace="~{apps/register/components/orgBasicInfoForm :: orgInfoScript}"></script>
<script th:replace="~{apps/register/components/orgUserInfoForm :: orgUserInfoScript}"></script>
<script th:replace="~{apps/register/components/emailChangeForm :: emailChangeScript}"></script>
<script th:replace="~{apps/register/components/newUserInfoForm :: newUserScript}"></script>
<script th:inline="javascript">
document.addEventListener('DOMContentLoaded', function () {
activateSelect();
let form = document.getElementById('registerForm');
let agreementForm = document.getElementById('agreementForm');
let btn_register = document.querySelector('.btn_register');
let isAuthVerified = false;
// registrationScenario를 hidden input에서 가져오도록 수정
let registrationScenario = document.getElementById('registrationScenario')?.value || 'new';
// hidden input의 값이 변경되면 registrationScenario 변수도 업데이트
const scenarioInput = document.getElementById('registrationScenario');
if (scenarioInput) {
scenarioInput.addEventListener('change', function () {
registrationScenario = this.value;
console.log('Registration Scenario Updated:', registrationScenario);
});
// MutationObserver를 사용하여 값 변경 감지
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
registrationScenario = scenarioInput.value;
console.log('Registration Scenario Updated (from mutation):', registrationScenario);
}
});
});
observer.observe(scenarioInput, {attributes: true});
// MutationObserver를 사용하여 값 변경 감지
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
registrationScenario = scenarioInput.value;
console.log('Registration Scenario Updated (from mutation):', registrationScenario);
}
});
});
observer.observe(scenarioInput, {attributes: true});
}
// 시나리오별 필수 필드 정의
const requiredFieldsByScenario = {
new: {
user: ['loginId', 'userName', 'password', 'password2', 'mobileNumber', 'authNumber'],
org: ['compRegNo', 'corpRegNo', 'orgName', 'ceoName', 'orgAddr', 'orgSectors', 'orgIndustryType', 'orgPhoneNumber', 'scPhoneNumber', 'serviceName', 'compRegFile', 'files']
},
retain: {
user: ['loginId', 'passwordConfirmIndividual'],
org: ['compRegNo', 'corpRegNo', 'orgName', 'ceoName', 'orgAddr', 'orgSectors', 'orgIndustryType', 'orgPhoneNumber', 'scPhoneNumber', 'serviceName', 'compRegFile', 'files']
},
change: {
user: ['loginId', 'passwordConfirmEmailChange', 'newLoginId'],
org: ['compRegNo', 'corpRegNo', 'orgName', 'ceoName', 'orgAddr', 'orgSectors', 'orgIndustryType', 'orgPhoneNumber', 'scPhoneNumber', 'serviceName', 'compRegFile', 'files']
}
};
// 시나리오별 필수 필드 정의
const requiredFieldsByScenario = {
new: {
user: ['loginId', 'userName', 'password', 'password2', 'mobileNumber', 'authNumber'],
org: ['compRegNo', 'corpRegNo', 'orgName', 'ceoName', 'orgAddr', 'orgSectors', 'orgIndustryType', 'orgPhoneNumber', 'scPhoneNumber', 'serviceName', 'compRegFile', 'files']
},
retain: {
user: ['loginId', 'passwordConfirmIndividual'],
org: ['compRegNo', 'corpRegNo', 'orgName', 'ceoName', 'orgAddr', 'orgSectors', 'orgIndustryType', 'orgPhoneNumber', 'scPhoneNumber', 'serviceName', 'compRegFile', 'files']
},
change: {
user: ['loginId', 'passwordConfirmEmailChange', 'newLoginId'],
org: ['compRegNo', 'corpRegNo', 'orgName', 'ceoName', 'orgAddr', 'orgSectors', 'orgIndustryType', 'orgPhoneNumber', 'scPhoneNumber', 'serviceName', 'compRegFile', 'files']
}
};
// 이메일 유효성 검사 함수
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
// 이메일 유효성 검사 함수
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function collectFormData() {
const form = document.createElement('form');
form.enctype = 'multipart/form-data';
form.method = 'POST';
form.action = '/signup/portalOrg';
form.style.display = 'none';
const fields = requiredFieldsByScenario[registrationScenario];
if (!fields) {
console.error('Unknown scenario');
return form;
}
// 시나리오 값 추가
const scenarioInput = document.getElementById('registrationScenario');
if (scenarioInput) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'registrationScenario';
input.value = scenarioInput.value;
form.appendChild(input);
} else {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'registrationScenario';
input.value = registrationScenario;
form.appendChild(input);
}
// user와 org 필드를 따로 처리
fields.user.forEach(fieldId => {
const element = document.getElementById(fieldId);
if (element) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = fieldId;
input.value = element.value;
form.appendChild(input);
}
});
fields.org.forEach(fieldId => {
const element = document.getElementById(fieldId);
if (element) {
if (element.type === 'file') {
const files = element.files;
if (files.length > 0) {
const input = document.createElement('input');
input.type = 'file';
input.name = fieldId;
input.files = files;
form.appendChild(input);
}
} else {
const input = document.createElement('input');
input.type = 'hidden';
input.name = fieldId;
input.value = element.value;
form.appendChild(input);
}
}
});
function collectFormData() {
const form = document.createElement('form');
form.enctype = 'multipart/form-data';
form.method = 'POST';
form.action = '/signup/portalOrg';
form.style.display = 'none';
// Add confirmPassword manually based on the scenario
if (registrationScenario === 'retain') {
const passwordConfirmIndividual = document.getElementById('passwordConfirmIndividual');
if (passwordConfirmIndividual) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'confirmPassword';
input.value = passwordConfirmIndividual.value;
form.appendChild(input);
}
} else if (registrationScenario === 'change') {
const passwordConfirmEmailChange = document.getElementById('passwordConfirmEmailChange');
if (passwordConfirmEmailChange) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'confirmPassword';
input.value = passwordConfirmEmailChange.value;
form.appendChild(input);
}
}
const fields = requiredFieldsByScenario[registrationScenario];
// 공통 필드 추가
if (document.getElementById('termsOfUse')) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'termsOfUse';
input.value = document.getElementById('termsOfUse').checked;
form.appendChild(input);
}
if (!fields) {
console.error('Unknown scenario');
return form;
}
if (document.getElementById('privacyCollect')) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'privacyCollect';
input.value = document.getElementById('privacyCollect').checked;
form.appendChild(input);
}
// 시나리오 값 추가
const scenarioInput = document.getElementById('registrationScenario');
if (scenarioInput) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'registrationScenario';
input.value = scenarioInput.value;
form.appendChild(input);
} else {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'registrationScenario';
input.value = registrationScenario;
form.appendChild(input);
}
// 시스템 필드 추가
const csrfToken = document.querySelector('input[name="_csrf"]');
if (csrfToken) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = '_csrf';
input.value = csrfToken.value;
form.appendChild(input);
}
// user와 org 필드를 따로 처리
fields.user.forEach(fieldId => {
const element = document.getElementById(fieldId);
if (element) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = fieldId;
input.value = element.value;
form.appendChild(input);
}
});
const registrationType = document.querySelector('input[name="registrationType"]');
if (registrationType) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'registrationType';
input.value = registrationType.value;
form.appendChild(input);
}
fields.org.forEach(fieldId => {
const element = document.getElementById(fieldId);
if (element) {
if (element.type === 'file') {
const files = element.files;
if (files.length > 0) {
const input = document.createElement('input');
input.type = 'file';
input.name = fieldId;
input.files = files;
form.appendChild(input);
}
} else {
const input = document.createElement('input');
input.type = 'hidden';
input.name = fieldId;
input.value = element.value;
form.appendChild(input);
}
}
});
console.log('Collected Form Data for scenario:',
document.getElementById('registrationScenario')?.value || registrationScenario);
return form;
}
// Add confirmPassword manually based on the scenario
if (registrationScenario === 'retain') {
const passwordConfirmIndividual = document.getElementById('passwordConfirmIndividual');
if (passwordConfirmIndividual) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'confirmPassword';
input.value = passwordConfirmIndividual.value;
form.appendChild(input);
}
} else if (registrationScenario === 'change') {
const passwordConfirmEmailChange = document.getElementById('passwordConfirmEmailChange');
if (passwordConfirmEmailChange) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'confirmPassword';
input.value = passwordConfirmEmailChange.value;
form.appendChild(input);
}
}
// 시나리오별 유효성 검사
function validateFormByScenario() {
// 공통 필드 추가
if (document.getElementById('termsOfUse')) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'termsOfUse';
input.value = document.getElementById('termsOfUse').checked;
form.appendChild(input);
}
const currentScenario = document.getElementById('registrationScenario')?.value || 'new';
console.log('Registration Scenario:', currentScenario);
if (document.getElementById('privacyCollect')) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'privacyCollect';
input.value = document.getElementById('privacyCollect').checked;
form.appendChild(input);
}
if (!currentScenario) {
console.log('Unknown scenario');
return false;
}
// 시스템 필드 추가
const csrfToken = document.querySelector('input[name="_csrf"]');
if (csrfToken) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = '_csrf';
input.value = csrfToken.value;
form.appendChild(input);
}
switch (registrationScenario) {
case 'new':
const loginIdElement = document.getElementById('loginId');
const userNameElement = document.getElementById('userName');
const registrationType = document.querySelector('input[name="registrationType"]');
if (registrationType) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'registrationType';
input.value = registrationType.value;
form.appendChild(input);
}
const isEmailValid = loginIdElement ? isValidEmail(loginIdElement.value) : false;
const hasUserName = userNameElement ? userNameElement.value.trim() !== '' : false;
const isPasswordValid = document.getElementById('isPasswordValid').value.trim() === 'true';
const isPasswordMatch = document.getElementById('isPasswordMatch').value.trim() === 'true';
console.log('Collected Form Data for scenario:',
document.getElementById('registrationScenario')?.value || registrationScenario);
return form;
}
console.log('New User Validation:', {
email: loginIdElement?.value,
isEmailValid,
isPasswordValid,
isPasswordMatch,
userName: userNameElement?.value,
hasUserName,
isAuthVerified,
requiredFields: currentScenario
});
return isEmailValid && isPasswordValid && isPasswordMatch && hasUserName && isAuthVerified;
// 시나리오별 유효성 검사
function validateFormByScenario() {
case 'retain':
const passwordConfirmElement = document.getElementById('passwordConfirmIndividual');
const hasPassword = passwordConfirmElement ? passwordConfirmElement.value.length >= 8 : false;
const retainLoginIdElement = document.getElementById('loginId');
const currentScenario = document.getElementById('registrationScenario')?.value || 'new';
console.log('Registration Scenario:', currentScenario);
console.log('Retain User Validation:', {
email: retainLoginIdElement?.value,
isEmailValid: retainLoginIdElement ? isValidEmail(retainLoginIdElement.value) : false,
passwordLength: passwordConfirmElement?.value.length,
hasPassword,
requiredFields: currentScenario
});
return retainLoginIdElement && isValidEmail(retainLoginIdElement.value) && hasPassword;
if (!currentScenario) {
console.log('Unknown scenario');
return false;
}
case 'change':
const changeLoginIdElement = document.getElementById('loginId');
const newLoginIdElement = document.getElementById('newLoginId');
const changePasswordElement = document.getElementById('passwordConfirmEmailChange');
const hasChangePassword = changePasswordElement ? changePasswordElement.value.length >= 8 : false;
switch (registrationScenario) {
case 'new':
const loginIdElement = document.getElementById('loginId');
const userNameElement = document.getElementById('userName');
console.log('Change Email Validation:', {
currentEmail: changeLoginIdElement?.value,
newEmail: newLoginIdElement?.value,
hasPassword: hasChangePassword,
requiredFields: currentScenario
});
return changeLoginIdElement && newLoginIdElement &&
isValidEmail(changeLoginIdElement.value) &&
hasChangePassword &&
isValidEmail(newLoginIdElement.value);
const isEmailValid = loginIdElement ? isValidEmail(loginIdElement.value) : false;
const hasUserName = userNameElement ? userNameElement.value.trim() !== '' : false;
const isPasswordValid = document.getElementById('isPasswordValid').value.trim() === 'true';
const isPasswordMatch = document.getElementById('isPasswordMatch').value.trim() === 'true';
default:
console.log('Unknown scenario');
return false;
}
}
console.log('New User Validation:', {
email: loginIdElement?.value,
isEmailValid,
isPasswordValid,
isPasswordMatch,
userName: userNameElement?.value,
hasUserName,
isAuthVerified,
requiredFields: currentScenario
});
return isEmailValid && isPasswordValid && isPasswordMatch && hasUserName && isAuthVerified;
// 공통 필드 유효성 검사
function validateCommonFields() {
case 'retain':
const passwordConfirmElement = document.getElementById('passwordConfirmIndividual');
const hasPassword = passwordConfirmElement ? passwordConfirmElement.value.length >= 8 : false;
const retainLoginIdElement = document.getElementById('loginId');
if(!validatePhoneNumber('org') || !validatePhoneNumber('sc')) {
return false;
}
console.log('Retain User Validation:', {
email: retainLoginIdElement?.value,
isEmailValid: retainLoginIdElement ? isValidEmail(retainLoginIdElement.value) : false,
passwordLength: passwordConfirmElement?.value.length,
hasPassword,
requiredFields: currentScenario
});
return retainLoginIdElement && isValidEmail(retainLoginIdElement.value) && hasPassword;
const validations = {
termsOfUse: $('#termsOfUse').prop('checked'),
privacyPolicy: $('#privacyCollect').prop('checked'),
compRegNo: $('#compRegNo').val().trim() !== '',
corpRegNo: $('#corpRegNo').val().trim() !== '',
orgName: $('#orgName').val().trim() !== '',
ceoName: $('#ceoName').val().trim() !== '',
orgAddr: $('#orgAddr').val().trim() !== '',
orgPhoneNumber: $('#orgPhoneNumber').val().trim() !== '',
scPhoneNumber: $('#scPhoneNumber').val().trim() !== '',
serviceName: $('#serviceName').val().trim() !== '',
compRegFile: $('#compRegFile').val().trim() !== ''
};
case 'change':
const changeLoginIdElement = document.getElementById('loginId');
const newLoginIdElement = document.getElementById('newLoginId');
const changePasswordElement = document.getElementById('passwordConfirmEmailChange');
const hasChangePassword = changePasswordElement ? changePasswordElement.value.length >= 8 : false;
console.log('Common Fields Validation:', validations);
return Object.values(validations).every(value => value === true);
}
console.log('Change Email Validation:', {
currentEmail: changeLoginIdElement?.value,
newEmail: newLoginIdElement?.value,
hasPassword: hasChangePassword,
requiredFields: currentScenario
});
return changeLoginIdElement && newLoginIdElement &&
isValidEmail(changeLoginIdElement.value) &&
hasChangePassword &&
isValidEmail(newLoginIdElement.value);
// 전체 폼 유효성 검사
function validateForm() {
const scenarioValidation = validateFormByScenario();
const commonValidation = validateCommonFields();
default:
console.log('Unknown scenario');
return false;
}
}
console.log('Form validation result:', {
scenarioValidation,
commonValidation,
registrationScenario
});
// 공통 필드 유효성 검사
function validateCommonFields() {
return scenarioValidation && commonValidation;
}
if(!validatePhoneNumber('org') || !validatePhoneNumber('sc')) {
return false;
}
let isSubmitting = false;
const validations = {
termsOfUse: $('#termsOfUse').prop('checked'),
privacyPolicy: $('#privacyCollect').prop('checked'),
compRegNo: $('#compRegNo').val().trim() !== '',
corpRegNo: $('#corpRegNo').val().trim() !== '',
orgName: $('#orgName').val().trim() !== '',
ceoName: $('#ceoName').val().trim() !== '',
orgAddr: $('#orgAddr').val().trim() !== '',
orgPhoneNumber: $('#orgPhoneNumber').val().trim() !== '',
scPhoneNumber: $('#scPhoneNumber').val().trim() !== '',
serviceName: $('#serviceName').val().trim() !== '',
compRegFile: $('#compRegFile').val().trim() !== ''
};
// 등록 버튼 클릭 이벤트
btn_register.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
console.log('Common Fields Validation:', validations);
return Object.values(validations).every(value => value === true);
}
// Prevent resubmission
if (isSubmitting) {
console.log('Form is already being submitted...');
return;
}
// 전체 폼 유효성 검사
function validateForm() {
const scenarioValidation = validateFormByScenario();
const commonValidation = validateCommonFields();
isSubmitting = true;
btn_register.disabled = true;
console.log('Form validation result:', {
scenarioValidation,
commonValidation,
registrationScenario
});
// Show loading overlay
document.getElementById('loadingOverlay').classList.add('active');
return scenarioValidation && commonValidation;
}
try {
form.classList.add('was-validated');
if (agreementForm) {
agreementForm.classList.add('was-validated');
}
let isSubmitting = false;
console.log('Validating form...');
// 등록 버튼 클릭 이벤트
btn_register.addEventListener('click', function (event) {
event.preventDefault();
event.stopPropagation();
if (validateForm()) {
console.log('Form validation passed, collecting form data...');
const form = collectFormData();
document.body.appendChild(form);
form.submit();
} else {
console.log('Form validation failed');
// Prevent resubmission
if (isSubmitting) {
console.log('Form is already being submitted...');
return;
}
// 현재 상태 출력
const scenarioValidation = validateFormByScenario();
const commonValidation = validateCommonFields();
isSubmitting = true;
btn_register.disabled = true;
try {
form.classList.add('was-validated');
if (agreementForm) {
agreementForm.classList.add('was-validated');
}
const isPasswordValid = document.getElementById('isPasswordValid')?.value.trim() === 'true';
const isPasswordMatch = document.getElementById('isPasswordMatch')?.value.trim() === 'true';
console.log('Validating form...');
console.log('Validation details:', {
scenarioValidation,
commonValidation,
registrationScenario,
isAuthVerified,
isPasswordValid,
isPasswordMatch
});
if (validateForm()) {
console.log('Form validation passed, collecting form data...');
const form = collectFormData();
document.body.appendChild(form);
form.submit();
} else {
console.log('Form validation failed');
// 현재 상태 출력
const scenarioValidation = validateFormByScenario();
const commonValidation = validateCommonFields();
const isPasswordValid = document.getElementById('isPasswordValid').value.trim() === 'true';
const isPasswordMatch = document.getElementById('isPasswordMatch').value.trim() === 'true';
console.log('Validation details:', {
scenarioValidation,
commonValidation,
registrationScenario,
isAuthVerified,
isPasswordValid,
isPasswordMatch
});
let errorMessage = '모든 필수 항목을 입력하고 약관에 동의해주세요.'
if (!scenarioValidation) {
customPopups.showAlert(errorMessage);
}
if (!commonValidation) {
customPopups.showAlert(errorMessage);
}
customPopups.showAlert(errorMessage);
// Reset submission state since validation failed
isSubmitting = false;
btn_register.disabled = false;
}
} catch (error) {
console.error('Form submission error:', error);
// Reset submission state on error
isSubmitting = false;
btn_register.disabled = false;
}
let errorMessage = '모든 필수 항목을 입력하고 약관에 동의해주세요.'
customPopups.showAlert(errorMessage);
// Reset submission state since validation failed
isSubmitting = false;
btn_register.disabled = false;
document.getElementById('loadingOverlay').classList.remove('active');
}
} catch (error) {
console.error('Form submission error:', error);
// Reset submission state on error
isSubmitting = false;
btn_register.disabled = false;
document.getElementById('loadingOverlay').classList.remove('active');
}
});
// 신규 이메일 중복 체크 응답 처리 함수
function handleNewEmailValidationResponse(response) {
if (response.valid) {
customPopups.showAlert(response.message || '신규 이메일 정보입니다.');
} else {
customPopups.showAlert(response.message || '이미 사용 중인 이메일입니다.');
}
// 신규 이메일 중복 체크 응답 처리 함수
function handleNewEmailValidationResponse(response) {
if (response.valid) {
customPopups.showAlert(response.message || '신규 이메일 정보입니다.');
} else {
customPopups.showAlert(response.message || '이미 사용 중인 이메일입니다.');
}
}
// HTMX 이벤트 리스너 - 모든 HTMX 응답을 중앙에서 처리
document.body.addEventListener('htmx:afterRequest', function (event) {
const targetId = event.detail.target.id;
let response;
try {
response = JSON.parse(event.detail.xhr.response);
} catch (e) {
console.error('응답 JSON 파싱 실패:', e);
customPopups.showAlert('서버 응답을 처리할 수 없습니다.');
return;
}
// 비밀번호 관련 검증은 컴포넌트의 로직을 따르도록 return
if (targetId === 'password-validation' || targetId === 'password-match-validation') {
return;
}
// 나머지 요청들에 대해서만 처리
if (event.detail.xhr.getResponseHeader('X-Custom-Handler') === 'registration-validation') {
if (response && response.message) {
customPopups.showAlert(response.message);
}
return;
}
// 기존 응답 처리 로직
if (targetId === 'email-validation') {
handleEmailValidationResponse(response);
} else if (targetId === 'new-email-validation') {
handleNewEmailValidationResponse(response);
}
// 사업자등록번호와 법인등록번호에 대한 커스텀 이벤트 디스패치
if (targetId === 'compRegNo' || targetId === 'corpRegNo') {
const customEvent = new CustomEvent('htmxResponse', {
detail: {
targetId: targetId,
response: response
}
});
document.dispatchEvent(customEvent);
}
});
// HTMX 이벤트 리스너 - 모든 HTMX 응답을 중앙에서 처리
document.body.addEventListener('htmx:afterRequest', function (event) {
const targetId = event.detail.target.id;
let response;
// 커스텀 이벤트 리스너
document.addEventListener('htmxResponse', function (event) {
const {targetId, response} = event.detail;
try {
response = JSON.parse(event.detail.xhr.response);
} catch (e) {
console.error('응답 JSON 파싱 실패:', e);
customPopups.showAlert('서버 응답을 처리할 수 없습니다.');
return;
}
if (targetId === 'compRegNo') {
customPopups.showAlert(response.message || (response.valid ? '유효한 사업자등록번호입니다.' : '유효하지 않은 사업자등록번호입니다.'));
} else if (targetId === 'corpRegNo') {
customPopups.showAlert(response.message || (response.valid ? '유효한 법인등록번호입니다.' : '유효하지 않은 법인등록번호입니다.'));
}
});
// 비밀번호 관련 검증은 컴포넌트의 로직을 따르도록 return
if (targetId === 'password-validation' || targetId === 'password-match-validation') {
return;
}
// 인증 완료 이벤트 리스너
document.addEventListener('authVerified', function () {
isAuthVerified = true;
});
// 나머지 요청들에 대해서만 처리
if (event.detail.xhr.getResponseHeader('X-Custom-Handler') === 'registration-validation') {
if (response && response.message) {
customPopups.showAlert(response.message);
}
return;
}
// 기존 응답 처리 로직
if (targetId === 'email-validation') {
handleEmailValidationResponse(response);
} else if (targetId === 'new-email-validation') {
handleNewEmailValidationResponse(response);
}
// 사업자등록번호와 법인등록번호에 대한 커스텀 이벤트 디스패치
if (targetId === 'compRegNo' || targetId === 'corpRegNo') {
const customEvent = new CustomEvent('htmxResponse', {
detail: {
targetId: targetId,
response: response
}
});
document.dispatchEvent(customEvent);
}
});
// 커스텀 이벤트 리스너
document.addEventListener('htmxResponse', function (event) {
const {targetId, response} = event.detail;
if (targetId === 'compRegNo') {
customPopups.showAlert(response.message || (response.valid ? '유효한 사업자등록번호입니다.' : '유효하지 않은 사업자등록번호입니다.'));
} else if (targetId === 'corpRegNo') {
customPopups.showAlert(response.message || (response.valid ? '유효한 법인등록번호입니다.' : '유효하지 않은 법인등록번호입니다.'));
}
});
// 인증 완료 이벤트 리스너
document.addEventListener('authVerified', function () {
isAuthVerified = true;
});
// 취소 버튼 클릭 시
document.getElementById('cancelButton').addEventListener('click', function () {
location.href = '/login';
});
});
</script>
// 취소 버튼 클릭 시
document.getElementById('cancelButton').addEventListener('click', function () {
location.href = '/login';
});
});
</script>
</th:block>
</body>
</html>
@@ -5,53 +5,70 @@
layout:decorate="~{layout/base_layout}">
<body>
<th:block th:fragment="agreementContent(termsOfUse, privacyCollect)">
<div class="terms_box t_top">
<p>약관 동의</p>
<!-- Agreement Section Header -->
<div class="org-section-header org-section-header--agreement">
<h3>약관 동의</h3>
<span class="required-badge">필수 동의</span>
</div>
<form id="agreementForm">
<div class="total_wrap">
<span class="inp_check b_tit">
<input type='checkbox' value='selectall' id="agree_all"/>
<label for="agree_all">전체동의</label>
</span>
<div class="total_type">
<div class="suv_ck">
<ul>
<li>
<span class="inp_check s_tit">
<input type='checkbox' name='termsOfUse' id="termsOfUse" required/>
<label for="termsOfUse">
<span>[필수]</span>「API Portal」 서비스 이용약관
</label>
</span>
<a class="toggle-btn" data-target="termsOfUseContent"></a>
<div id="termsOfUseContent" class="txt_wrap scrollbar" style="display:none;">
<div th:utext="${termsOfUse?.contents ?: '이용약관 내용을 불러올 수 없습니다.'}"></div>
</div>
</li>
</ul>
<form id="agreementForm" class="agreement-form">
<!-- Agree All Checkbox -->
<div class="agreement-all-section">
<label class="agreement-checkbox-label">
<input type="checkbox" id="agree_all" class="agreement-checkbox-input">
<span class="agreement-checkbox-custom"></span>
<span class="agreement-checkbox-text agreement-all-text">전체동의</span>
</label>
</div>
<!-- Divider -->
<div class="agreement-divider"></div>
<!-- Individual Agreements -->
<div class="agreement-items-list">
<!-- Terms of Use -->
<div class="agreement-item-row">
<label class="agreement-checkbox-label">
<input type="checkbox" name="termsOfUse" id="termsOfUse" class="agreement-checkbox-input" required>
<span class="agreement-checkbox-custom"></span>
<span class="agreement-checkbox-text">
<span class="agreement-required">[필수]</span>
<span class="agreement-title">이용약관 동의</span>
</span>
</label>
<button type="button" class="agreement-toggle-icon" data-target="termsOfUseContent">
<i class="fas fa-chevron-down"></i>
</button>
</div>
<div id="termsOfUseContent" class="agreement-content" style="display:none;">
<div class="agreement-scroll">
<div th:utext="${termsOfUse?.contents ?: '이용약관 내용을 불러올 수 없습니다.'}"></div>
</div>
<!-- 개인정보수집동의서 추가 -->
<div class="suv_ck">
<ul>
<li>
<span class="inp_check s_tit">
<input type='checkbox' name='privacyCollect' id="privacyCollect" required/>
<th:block th:switch="${registrationType}">
<label for="privacyCollect" th:case="'personal'">
<span>[필수]</span> Kbank API Portal 회원가입을 위한 개인정보 수집•이용 동의서 (개인회원용)
</label>
<label for="privacyCollect" th:case="'corporate'">
<span>[필수]</span> Kbank API Portal 회원가입을 위한 개인정보 수집•이용 동의서 (법인회원용)
</label>
</th:block>
</span>
<a class="toggle-btn" data-target="privacyCollectContent"></a>
<div id="privacyCollectContent" class="txt_wrap scrollbar" style="display:none;">
<div th:utext="${privacyCollect?.contents ?: '개인정보수집동의서 내용을 불러올 수 없습니다.'}"></div>
</div>
</li>
</ul>
</div>
<!-- Privacy Policy -->
<div class="agreement-item-row">
<label class="agreement-checkbox-label">
<input type="checkbox" name="privacyCollect" id="privacyCollect" class="agreement-checkbox-input" required>
<span class="agreement-checkbox-custom"></span>
<th:block th:switch="${registrationType}">
<span class="agreement-checkbox-text" th:case="'personal'">
<span class="agreement-required">[필수]</span>
<span class="agreement-title">개인정보 수집 및 이용 동의</span>
</span>
<span class="agreement-checkbox-text" th:case="'corporate'">
<span class="agreement-required">[필수]</span>
<span class="agreement-title">개인정보 수집 및 이용 동의</span>
</span>
</th:block>
</label>
<button type="button" class="agreement-toggle-icon" data-target="privacyCollectContent">
<i class="fas fa-chevron-down"></i>
</button>
</div>
<div id="privacyCollectContent" class="agreement-content" style="display:none;">
<div class="agreement-scroll">
<div th:utext="${privacyCollect?.contents ?: '개인정보수집동의서 내용을 불러올 수 없습니다.'}"></div>
</div>
</div>
</div>
@@ -61,49 +78,56 @@
<script th:fragment="agreementScript">
document.addEventListener('DOMContentLoaded', function () {
const agreeAll = document.getElementById('agree_all');
const agreementChecks = document.querySelectorAll('.inp_check.s_tit input[type="checkbox"]');
const toggleBtns = document.querySelectorAll('.toggle-btn');
const agreementChecks = document.querySelectorAll('.agreement-checkbox-input[required]');
const toggleIcons = document.querySelectorAll('.agreement-toggle-icon');
// 전체 동의 체크박스 이벤트
agreeAll.addEventListener('change', function () {
agreementChecks.forEach(check => {
check.checked = this.checked;
if (agreeAll) {
agreeAll.addEventListener('change', function () {
agreementChecks.forEach(check => {
check.checked = this.checked;
});
});
});
}
// 개별 체크박스 이벤트
agreementChecks.forEach(check => {
check.addEventListener('change', function () {
agreeAll.checked = Array.from(agreementChecks).every(c => c.checked);
if (agreeAll) {
agreeAll.checked = Array.from(agreementChecks).every(c => c.checked);
}
});
});
// 토글 버튼 이벤트
toggleBtns.forEach(btn => {
btn.addEventListener('click', function (e) {
// 토글 아이콘 이벤트
toggleIcons.forEach(icon => {
icon.addEventListener('click', function (e) {
e.preventDefault();
const targetId = this.getAttribute('data-target');
const targetContent = document.getElementById(targetId);
const parentSection = this.closest('.suv_ck');
const parentRow = this.closest('.agreement-item-row');
// 현재 섹션이 이미 열려있는지 확인
const isCurrentlyOpen = targetContent.style.display === 'block';
document.querySelectorAll('.txt_wrap').forEach(content => {
// 모든 섹션 닫기
document.querySelectorAll('.agreement-content').forEach(content => {
content.style.display = 'none';
});
document.querySelectorAll('.toggle-btn').forEach(button => {
document.querySelectorAll('.agreement-toggle-icon').forEach(button => {
button.classList.remove('active');
});
document.querySelectorAll('.suv_ck').forEach(section => {
section.classList.remove('active');
document.querySelectorAll('.agreement-item-row').forEach(item => {
item.classList.remove('active');
});
// 현재 섹션이 닫혀있었다면 열기
if (!isCurrentlyOpen) {
targetContent.style.display = 'block';
this.classList.add('active');
parentSection.classList.add('active');
if (parentRow) {
parentRow.classList.add('active');
}
setTimeout(() => {
targetContent.scrollIntoView({
@@ -1,65 +1,76 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
<html lang="ko" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/kbank_base_layout}">
layout:decorate="~{layout/kjbank_signup_layout}">
<body>
<section layout:fragment="contentFragment" class="content">
<script th:src="@{/js/htmx.min.js}"></script>
<div class="content_wrap">
<div class="sub_title2">
<h2 class="title add" th:if="${!isInvited}">개인회원가입</h2>
<h2 class="title add" th:if="${isInvited}">법인회원가입</h2>
</div>
<div class="inner i_cs h_inner2">
<div class="top_info" th:if="${!isInvited}">
<ul>
<li>서비스 또는 API 사용을 원하실 경우 법인회원 승인 후 이용하실 수 있습니다.</li>
</ul>
</div>
<!-- 약관동의 섹션 -->
<input type="hidden" id="csrf" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<th:block th:replace="~{apps/register/userAgreementContent :: agreementContent(${termsOfUse}, ${privacyCollect})}"></th:block>
<!-- 기본 정보 입력 섹션 -->
<form name="portalUser" id="registerForm" role="form" th:action="@{/signup/portalUser}"
th:object="${portalUser}" method="post">
<!-- 법인 정보 섹션, 초대받은 경우에만 -->
<div class="terms_box" th:if="${isInvited}">
<p>법인 정보</p>
</div>
<section layout:fragment="title">
<div class="org-page-title-banner">
<img th:src="@{/img/user_register_title.png}" alt="개인회원가입" class="title-image">
<h1 th:text="${!isInvited ? '개인회원가입' : '법인회원가입'}">개인회원가입</h1>
</div>
</section>
<div class="form_type" th:if="${isInvited}">
<div class="info1">
<p class="title w_tit">
<span>법인명</span>
</p>
<div class="info_line">
<div class="info_box1 w_inp4">
<input type="text" name="companyName" class="common_input_type_1"
th:classappend="${isInvited ? 'input-readonly' : ''}" th:value="${orgName}" readonly>
</div>
</div>
</div>
</div>
<!-- end 법인 정보 섹션, 초대받은 경우에만 -->
<div class="terms_box">
<p>기본 정보</p>
<span class="dot">필수 입력란입니다</span>
</div>
<div class="form_type" th:if="${registrationType == isInvited ? 'corporate' :'personal'}">
<th:block th:replace="~{apps/register/components/commonUserInfoForm :: commonUserInfo}"></th:block>
<th:block th:replace="~{apps/register/components/newUserInfoForm :: newUserForm}"></th:block>
</div>
<div class="form_type">
<div class="btn_application">
<a type="button" class="common_btn_type_1 gray" id="cancelButton"><span>취소</span></a>
<div class="btn_gap"></div>
<a type="button" class="common_btn_type_1 btn_register"><span>가입신청</span></a>
</div>
</div>
</form>
<section layout:fragment="contentFragment">
<div class="org-register-page">
<div class="org-register-container">
<!-- Hidden Fields -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<input type="hidden" name="registrationType" th:value="${registrationType}"/>
<!-- Alert Container -->
<div id="alertContainer"></div>
<!-- Info Notice -->
<div class="org-info-notice" th:if="${!isInvited}">
<ul>
<li>서비스 또는 API 사용을 원하실 경우 법인회원 승인 후 이용하실 수 있습니다.</li>
</ul>
</div>
<!-- Agreement Section -->
<th:block th:replace="~{apps/register/userAgreementContent :: agreementContent(${termsOfUse}, ${privacyCollect})}"></th:block>
<form name="portalUser" id="registerForm" role="form" th:action="@{/signup/portalUser}"
th:object="${portalUser}" method="post">
<!-- Corporate Information Section (invited users only) -->
<div th:if="${isInvited}">
<div class="org-section-header org-section-header--agreement">
<h3>법인 정보</h3>
</div>
<div class="org-form-group">
<label class="org-form-label">법인명</label>
<div class="org-form-input-wrapper">
<input type="text" name="companyName" class="org-form-input" th:value="${orgName}" readonly>
</div>
</div>
</div>
<!-- Basic Information Section -->
<div class="org-section-header org-section-header--agreement" th:style="${isInvited ? 'margin-top: 48px;' : ''}">
<h3>기본 정보</h3>
<span class="required-badge">필수 입력</span>
</div>
<div th:if="${registrationType == (isInvited ? 'corporate' : 'personal')}">
<th:block th:replace="~{apps/register/components/commonUserInfoForm :: commonUserInfo}"></th:block>
<th:block th:replace="~{apps/register/components/newUserInfoForm :: newUserForm}"></th:block>
</div>
<!-- Action Buttons -->
<div class="org-action-buttons">
<button type="button" class="org-btn org-btn-secondary" id="cancelButton">취소</button>
<button type="button" class="org-btn org-btn-primary btn_register">가입신청</button>
</div>
</form>
<!-- Loading Overlay -->
<div class="org-loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
</div>
</div>
</div>
</section>
<th:block layout:fragment="contentScript">
<script th:if="${error}" th:inline="javascript">
@@ -70,128 +81,119 @@
<script th:replace="~{apps/register/userAgreementContent :: agreementScript}"></script>
<script th:replace="~{apps/register/components/commonUserInfoForm :: commonUserInfoScript}"></script>
<script th:replace="~{apps/register/components/newUserInfoForm :: newUserScript}"></script>
<script>
let form = document.getElementById('registerForm');
let agreementForm = document.getElementById('agreementForm');
let btn_register = document.querySelector('.btn_register');
let isAuthVerified = false;
<script th:inline="javascript">
document.addEventListener('DOMContentLoaded', function () {
let form = document.getElementById('registerForm');
let agreementForm = document.getElementById('agreementForm');
let btn_register = document.querySelector('.btn_register');
let isAuthVerified = false;
// 인증 완료 이벤트 리스너
document.addEventListener('authVerified', function() {
isAuthVerified = true;
});
// 인증 완료 이벤트 리스너
document.addEventListener('authVerified', function() {
isAuthVerified = true;
});
// 이메일 유효성 검사 함수
function isValidEmail(email) {
const safeEmailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
return safeEmailRegex.test(email);
}
// 폼 유효성 검사 함수
function checkFormValidity() {
return isValidEmail($('#loginId').val()) &&
($('#password').val() === $('#password2').val()) &&
($('#userName').val().trim() !== '') &&
$('#termsOfUse').prop('checked') &&
$('#privacyCollect').prop('checked') &&
(combineMobileNumber('#mobileNumber') !== null) &&
isAuthVerified; // 인증 여부 확인
}
let isSubmitting = false;
// 등록 버튼 클릭 이벤트
btn_register.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
// Prevent resubmission
if (isSubmitting) {
console.log('Form is already being submitted...');
return;
// 이메일 유효성 검사 함수
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
isSubmitting = true;
btn_register.disabled = true;
// 폼 유효성 검사 함수
function checkFormValidity() {
const loginIdElement = $('#loginId');
const userNameElement = $('#userName');
const termsOfUseElement = $('#termsOfUse');
const privacyCollectElement = $('#privacyCollect');
const mobileNumberElement = $('#mobileNumber');
const isPasswordValid = $('#isPasswordValid').val() === 'true';
const isPasswordMatch = $('#isPasswordMatch').val() === 'true';
form.classList.add('was-validated');
agreementForm.classList.add('was-validated');
return isValidEmail(loginIdElement.val()) &&
isPasswordValid &&
isPasswordMatch &&
userNameElement.val().trim() !== '' &&
termsOfUseElement.prop('checked') &&
privacyCollectElement.prop('checked') &&
mobileNumberElement.val().trim() !== '' &&
isAuthVerified;
}
if (checkFormValidity() && form.checkValidity() && agreementForm.checkValidity()) {
// 약관 동의 폼의 데이터를 hidden input으로 추가
let agreementFormData = new FormData(agreementForm);
for (let pair of agreementFormData.entries()) {
if (!form.querySelector('input[name="' + pair[0] + '"]')) {
let input = document.createElement('input');
input.type = 'hidden';
input.name = pair[0];
input.value = pair[1];
form.appendChild(input);
}
let isSubmitting = false;
// 등록 버튼 클릭 이벤트
btn_register.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
// Prevent resubmission
if (isSubmitting) {
console.log('Form is already being submitted...');
return;
}
// 불필요한 hidden input 제거
const unnecessaryFields = ['isPasswordValid', 'isPasswordMatch'];
unnecessaryFields.forEach(fieldName => {
const field = form.querySelector(`input[name="${fieldName}"]`);
if (field) {
field.remove();
isSubmitting = true;
btn_register.disabled = true;
// Show loading overlay
document.getElementById('loadingOverlay').classList.add('active');
try {
form.classList.add('was-validated');
if (agreementForm) {
agreementForm.classList.add('was-validated');
}
});
// authCompletedYn 값 설정
const authCompletedField = document.createElement('input');
authCompletedField.type = 'hidden';
authCompletedField.name = 'authCompletedYn';
authCompletedField.value = isAuthVerified ? 'Y' : 'N';
form.appendChild(authCompletedField);
if (checkFormValidity() && form.checkValidity() && (agreementForm ? agreementForm.checkValidity() : true)) {
// 약관 동의 폼의 데이터를 hidden input으로 추가
if (agreementForm) {
let agreementFormData = new FormData(agreementForm);
for (let pair of agreementFormData.entries()) {
if (!form.querySelector('input[name="' + pair[0] + '"]')) {
let input = document.createElement('input');
input.type = 'hidden';
input.name = pair[0];
input.value = pair[1];
form.appendChild(input);
}
}
}
form.submit();
} else {
customPopups.showAlert('모든 필수 항목을 입력하고 약관에 동의해주세요.');
isSubmitting = false;
btn_register.disabled = false;
}
});
// 불필요한 hidden input 제거
const unnecessaryFields = ['isPasswordValid', 'isPasswordMatch'];
unnecessaryFields.forEach(fieldName => {
const field = form.querySelector(`input[name="${fieldName}"]`);
if (field) {
field.remove();
}
});
// 취소버튼 클릭시
document.getElementById('cancelButton').addEventListener('click', function() {
location.href = '/login';
});
// authCompletedYn 값 설정
const authCompletedField = document.createElement('input');
authCompletedField.type = 'hidden';
authCompletedField.name = 'authCompletedYn';
authCompletedField.value = isAuthVerified ? 'Y' : 'N';
form.appendChild(authCompletedField);
// HTMX 이벤트 리스너 - 모든 HTMX 응답을 중앙에서 처리
document.body.addEventListener('htmx:afterRequest', function(event) {
const targetId = event.detail.target.id;
let response;
try {
response = JSON.parse(event.detail.xhr.response);
} catch (e) {
console.error('응답 JSON 파싱 실패:', e);
customPopups.showAlert('서버 응답을 처리할 수 없습니다.');
return;
}
// 커스텀 헤더를 체크하여 등록 검증 요청 처리
if (event.detail.xhr.getResponseHeader('X-Custom-Handler') === 'registration-validation') {
if (response && response.message) {
if (response.status === 'SUCCESS') {
customPopups.showSuccess(response.message);
form.submit();
} else {
customPopups.showAlert(response.message);
customPopups.showAlert('모든 필수 항목을 입력하고 약관에 동의해주세요.');
isSubmitting = false;
btn_register.disabled = false;
document.getElementById('loadingOverlay').classList.remove('active');
}
} catch (error) {
console.error('Form submission error:', error);
isSubmitting = false;
btn_register.disabled = false;
document.getElementById('loadingOverlay').classList.remove('active');
}
return;
}
});
// 응답 처리 로직
if (targetId === 'email-validation') {
// commonUserInfoForm의 함수 호출
handleEmailValidationResponse(response);
} else if (targetId === 'password-validation' || targetId === 'password-match-validation') {
// 비밀번호 검증은 컴포넌트에서 처리하도록 함
return;
}
// 취소 버튼 클릭 시
document.getElementById('cancelButton').addEventListener('click', function() {
location.href = '/login';
});
});
</script>
</th:block>