Files
kngil_home/kngil/js/popup.js
2026-01-30 17:20:52 +09:00

681 lines
22 KiB
JavaScript

/**
* Popup Controller Module
* 팝업 관련 기능을 관리하는 모듈
*/
(function() {
'use strict';
// ============================================
// Configuration
// ============================================
const CONFIG = {
SELECTORS: {
btnClose: '.btn-close',
btnMapClose: '.btn-map-close',
popupWrap: '.popup-wrap',
popupSitemap: '.popup-sitemap',
domainList: '#domain-list',
customDomain: '#custom-domain',
certNumber: '.cert-number',
code: '.code',
check: '.check',
checkComplete: '.check.complete',
timer: '.timer',
findEmail: '.find-email',
findPh: '.find-ph',
btnId: '.btn-id',
btnPw: '.btn-pw',
contentId: '.content.id',
contentPw: '.content.pw',
termsWrap: '.terms-wrap',
checkboxWrap: '.checkbox-wrap.all',
joinBtnWrap: '.join-btn-wrap',
popInputWrap: '.pop-input-wrap',
joinProgress: '.join-progress',
joinStep: '.join-step',
tabPrivacy: '.tab-privacy',
tabAgreement: '.tab-agreement',
tabContentPri: '.tab-content.pri',
tabContentAgr: '.tab-content.agr',
tabWrap: '.tab-menu',
contentsWrap: '.contents_wrap',
messages: '.messages'
},
TIMER: {
DURATION: 60 * 3, // 3분
INTERVAL: 1000 // 1초
},
CLASSES: {
on: 'on',
none: 'none',
show: 'show',
hide: 'hide',
complete: 'complete'
}
};
// ============================================
// Utility Functions
// ============================================
const Utils = {
/**
* Lenis 스크롤 시작
*/
startLenis() {
if (typeof lenis !== 'undefined' && lenis) {
lenis.start();
}
},
/**
* 스크롤 복원
*/
restoreScroll() {
$('body').css('overflow', '');
this.startLenis();
}
};
// ============================================
// Popup Close Controller
// ============================================
const PopupCloseController = {
init() {
// 팝업 닫기 버튼
$(document).on('click', CONFIG.SELECTORS.btnClose, () => {
$(CONFIG.SELECTORS.popupWrap).hide();
Utils.restoreScroll();
});
// 사이트맵 닫기 버튼
$(document).on('click', CONFIG.SELECTORS.btnMapClose, () => {
$(CONFIG.SELECTORS.popupSitemap).hide();
Utils.restoreScroll();
});
}
};
// ============================================
// Email Domain Controller
// ============================================
const EmailDomainController = {
init() {
// ID 기반 선택자 (기존 호환성)
$(document).on('change', CONFIG.SELECTORS.domainList, function() {
const $select = $(this);
const $selectBox = $select.closest('.select-box');
const $customDomain = $selectBox.find(CONFIG.SELECTORS.customDomain);
if ($select.val() === 'type') {
// 직접입력 선택 시 d-none 클래스 제거
$customDomain.removeClass('d-none').focus();
} else {
// 다른 선택지 선택 시 d-none 클래스 추가 및 값 초기화
$customDomain.addClass('d-none').val('');
}
});
// 클래스 기반 선택자 (.domain-list)
$(document).on('change', '.domain-list', function() {
const $select = $(this);
const $inputBox = $select.closest('.input-box');
const $customDomain = $inputBox.find('.domain-domain');
if ($select.val() === 'type') {
// 직접입력 선택 시 d-none 클래스 제거
$customDomain.removeClass('d-none').focus();
} else {
// 다른 선택지 선택 시 d-none 클래스 추가 및 값 초기화
$customDomain.addClass('d-none').val('');
}
});
}
};
// ============================================
// Timer Controller
// ============================================
// const TimerController = {
// interval: null,
// display: null,
// init() {
// this.display = $(CONFIG.SELECTORS.timer);
// // 인증번호 버튼 클릭 시
// $(document).on('click', CONFIG.SELECTORS.certNumber, () => {
// $(CONFIG.SELECTORS.code).show();
// this.start();
// });
// // 확인 버튼 클릭 시
// $(document).on('click', CONFIG.SELECTORS.check, function() {
// $(this).hide();
// $(CONFIG.SELECTORS.checkComplete).show();
// TimerController.stop();
// $(CONFIG.SELECTORS.timer).remove();
// });
// // 로그인 페이지 휴대폰 인증 폼의 인증 링크 요청 버튼 클릭 시
// $(document).on('submit', '#pop_login .tab-content.phone form', (e) => {
// e.preventDefault();
// const $form = $(e.target);
// const $timer = $form.find('.timer');
// const $infoBox = $form.find('.info-box');
// // 타이머와 info-box 표시
// // $timer.removeClass('hide');
// $timer.removeClass('d-none');
// $infoBox.removeClass('d-none');
// // 타이머 시작 (해당 폼 내의 타이머만)
// if ($timer.length) {
// this.stop();
// this.display = $timer;
// this.start();
// }
// });
// // 초기 타이머 시작 (다른 페이지에서 사용하는 경우를 위해)
// // 숨겨지지 않은 타이머만 자동 시작
// const $visibleTimers = $(CONFIG.SELECTORS.timer).not('.hide').not('.d-none');
// if ($visibleTimers.length > 0) {
// this.display = $visibleTimers.first();
// this.start();
// }
// },
// start() {
// this.stop();
// this.startTimer(CONFIG.TIMER.DURATION, this.display);
// },
// stop() {
// if (this.interval) {
// clearInterval(this.interval);
// this.interval = null;
// }
// },
// startTimer(duration, display) {
// let timer = duration;
// const updateTimer = () => {
// const minutes = Math.floor(timer / 60);
// const seconds = timer % 60;
// const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes;
// const formattedSeconds = seconds < 10 ? `0${seconds}` : seconds;
// if (display && display.length) {
// display.text(`${formattedMinutes}:${formattedSeconds}`);
// }
// if (--timer < 0) {
// this.stop();
// if (display && display.length) {
// display.text('00:00');
// }
// }
// };
// updateTimer();
// this.interval = setInterval(updateTimer, CONFIG.TIMER.INTERVAL);
// }
// };
// ============================================
// Find Account Controller
// ============================================
const FindAccountController = {
/**
* 라디오 버튼 전환 처리
* @param {jQuery} $radio - 클릭된 라디오 버튼
* @param {string} type - 'ph' 또는 'email'
*/
switchRadio($radio, type) {
const $radioWrap = $radio.closest('.radio-wrap');
const $tabContent = $radioWrap.closest('.tab-content');
// 같은 라디오 그룹의 다른 라디오 버튼들
const $otherRadio = type === 'ph'
? $radioWrap.find(CONFIG.SELECTORS.findEmail)
: $radioWrap.find(CONFIG.SELECTORS.findPh);
// 라디오 버튼 상태 변경
$otherRadio.removeClass(CONFIG.CLASSES.on).prop('checked', false);
$radio.addClass(CONFIG.CLASSES.on).prop('checked', true);
// 테이블 표시/숨김 (해당 탭 콘텐츠 내에서만)
if (type === 'ph') {
$tabContent.find('table.email').hide();
$tabContent.find('table.ph').show();
} else {
$tabContent.find('table.ph').hide();
$tabContent.find('table.email').show();
}
},
/**
* 초기 상태 설정
*/
initRadioState() {
// 각 탭 콘텐츠별로 초기 상태 설정
$('.tab-content').each(function() {
const $tabContent = $(this);
const $checkedRadio = $tabContent.find('.radio-wrap input[type="radio"]:checked');
if ($checkedRadio.length) {
if ($checkedRadio.hasClass('find-ph')) {
$tabContent.find('table.ph').show();
$tabContent.find('table.email').hide();
} else if ($checkedRadio.hasClass('find-email')) {
$tabContent.find('table.email').show();
$tabContent.find('table.ph').hide();
}
}
});
},
init() {
// 이메일 찾기 라디오 버튼 클릭
$(document).on('click', CONFIG.SELECTORS.findEmail, function(e) {
e.preventDefault();
FindAccountController.switchRadio($(this), 'email');
});
// 전화번호 찾기 라디오 버튼 클릭
$(document).on('click', CONFIG.SELECTORS.findPh, function(e) {
e.preventDefault();
FindAccountController.switchRadio($(this), 'ph');
});
// 라디오 버튼 변경 이벤트 (체크 상태 동기화)
$(document).on('change', '.radio-wrap input[type="radio"]', function() {
const $radio = $(this);
if ($radio.hasClass('find-ph')) {
FindAccountController.switchRadio($radio, 'ph');
} else if ($radio.hasClass('find-email')) {
FindAccountController.switchRadio($radio, 'email');
}
});
// 초기 상태 설정
this.initRadioState();
}
};
// ============================================
// Login Tab Controller
// ============================================
const LoginTabController = {
init() {
// ID 찾기 탭
$(document).on('click', CONFIG.SELECTORS.btnId, () => {
$(CONFIG.SELECTORS.btnId).addClass(CONFIG.CLASSES.on);
$(CONFIG.SELECTORS.btnPw).removeClass(CONFIG.CLASSES.on);
$(CONFIG.SELECTORS.contentId).show();
$(CONFIG.SELECTORS.contentPw).hide();
});
// 비밀번호 찾기 탭
$(document).on('click', CONFIG.SELECTORS.btnPw, () => {
$(CONFIG.SELECTORS.btnPw).addClass(CONFIG.CLASSES.on);
$(CONFIG.SELECTORS.btnId).removeClass(CONFIG.CLASSES.on);
$(CONFIG.SELECTORS.contentPw).show();
$(CONFIG.SELECTORS.contentId).hide();
});
// 도메인 선택 (중복 제거)
$(CONFIG.SELECTORS.domainList).on('change', function() {
const $customDomain = $(CONFIG.SELECTORS.customDomain);
if ($(this).val() === 'type') {
$customDomain.show();
} else {
$customDomain.hide();
}
});
}
};
// ============================================
// Terms Agreement Controller
// ============================================
const TermsAgreementController = {
init() {
// 약관 동의 상태 업데이트
this.updateJoinButton();
// 전체 동의 체크박스 (기존 호환성)
$(CONFIG.SELECTORS.checkboxWrap).find('input[type="checkbox"]').on('change', (e) => {
const isChecked = $(e.target).is(':checked');
$(CONFIG.SELECTORS.termsWrap).find('input[type="checkbox"]').prop('checked', isChecked);
this.updateJoinButton();
});
// .chk-all 클래스를 가진 체크박스 (전체 동의)
$(document).on('change click', '.chk-all', function(e) {
e.stopPropagation(); // 이벤트 전파 중지
const $chkAll = $(this);
const isChecked = $chkAll.is(':checked');
const $termsWrap = $chkAll.closest('.terms-wrap');
// .chk-all을 제외한 모든 체크박스 선택/해제
$termsWrap.find('input[type="checkbox"]:not(.chk-all)').prop('checked', isChecked);
// updateJoinButton 호출 시 무한 루프 방지를 위해 플래그 설정
TermsAgreementController._updatingFromChkAll = true;
TermsAgreementController.updateJoinButton();
TermsAgreementController._updatingFromChkAll = false;
});
// 개별 체크박스
$(CONFIG.SELECTORS.termsWrap).find('input[type="checkbox"]').on('change', () => {
this.updateJoinButton();
});
// .terms-wrap 내의 개별 체크박스 (chk-all 제외)
$(document).on('change', '.terms-wrap input[type="checkbox"]:not(.chk-all)', () => {
this.updateJoinButton();
});
},
updateJoinButton() {
// .terms-wrap 내의 모든 체크박스 확인 (chk-all 제외)
const $allTermsWraps = $('.terms-wrap');
let allChecked = true;
$allTermsWraps.each(function() {
const $checkboxes = $(this).find('input[type="checkbox"]:not(.chk-all)');
const $checked = $checkboxes.filter(':checked');
if ($checkboxes.length > 0 && $checkboxes.length !== $checked.length) {
allChecked = false;
return false; // break
}
});
// .chk-all 체크박스 상태 업데이트 (무한 루프 방지)
if (!TermsAgreementController._updatingFromChkAll) {
$('.chk-all').prop('checked', allChecked);
}
// 전체 동의 체크박스 상태 업데이트 (기존 호환성)
$(CONFIG.SELECTORS.checkboxWrap).find('input[type="checkbox"]').prop('checked', allChecked);
// 버튼 상태 업데이트
const $joinBtnWrap = $(CONFIG.SELECTORS.joinBtnWrap);
if (allChecked) {
$joinBtnWrap.removeClass(CONFIG.CLASSES.none);
$joinBtnWrap.find('button').prop('disabled', false);
} else {
$joinBtnWrap.addClass(CONFIG.CLASSES.none);
$joinBtnWrap.find('button').prop('disabled', true);
}
}
};
// ============================================
// Join Completion Controller
// ============================================
const JoinCompletionController = {
init() {
// 전송완료
$(document).on('click', '.pw ' + CONFIG.SELECTORS.joinBtnWrap + ' button', () => {
$(CONFIG.SELECTORS.contentsWrap).children().not(CONFIG.SELECTORS.messages).hide();
$(CONFIG.SELECTORS.messages).show();
});
// 가입완료
$(document).on('click', '.join.completion ' + CONFIG.SELECTORS.joinBtnWrap + ' button', () => {
$(CONFIG.SELECTORS.popInputWrap).find('form').children().not(CONFIG.SELECTORS.messages).hide();
$(CONFIG.SELECTORS.messages).show();
// 진행 단계 업데이트
$(CONFIG.SELECTORS.joinProgress).find(CONFIG.SELECTORS.joinStep).removeClass(CONFIG.CLASSES.on);
$(CONFIG.SELECTORS.joinProgress).find(CONFIG.SELECTORS.joinStep).eq(2).addClass(CONFIG.CLASSES.on);
});
}
};
// ============================================
// Universal Tab Controller
// ============================================
const TabController = {
/**
* 탭 클래스명에서 콘텐츠 클래스명 추출
* @param {jQuery} $tab - 탭 요소
* @returns {string} 콘텐츠 클래스명
*/
getContentClass($tab) {
// 탭 클래스명에서 tab- 접두사 제거
const tabClasses = $tab.attr('class').split(' ');
let contentClass = '';
for (let i = 0; i < tabClasses.length; i++) {
const className = tabClasses[i];
if (className.startsWith('tab-')) {
const tabName = className.replace('tab-', '');
// 특수 케이스 매핑
const specialCases = {
'privacy': 'pri',
'agreement': 'agr'
};
contentClass = specialCases[tabName] || tabName;
break;
}
}
return contentClass;
},
/**
* 슬라이딩 인디케이터 위치 업데이트 (round 타입 탭 메뉴용)
* @param {jQuery} $tabMenu - 탭 메뉴 요소
* @param {jQuery} $activeTab - 활성화된 탭 요소
*/
updateSliderPosition($tabMenu, $activeTab) {
// round 클래스가 있는 경우에만 처리
if (!$tabMenu.hasClass('round')) {
return;
}
const $tabs = $tabMenu.find('li');
const activeIndex = $tabs.index($activeTab);
const tabCount = $tabs.length;
if (tabCount === 0) return;
// 탭 메뉴의 실제 너비와 패딩 확인
const tabMenuWidth = $tabMenu.width();
const padding = 0; // padding: 4px
const gap = 4; // gap: 4px
// 사용 가능한 너비 (패딩 제외)
const availableWidth = tabMenuWidth - (padding * 2);
// 각 탭의 너비 계산 (gap 포함)
// gap은 탭 사이에만 있으므로, 탭 개수 - 1개의 gap이 있음
const totalGapWidth = gap * (tabCount - 1);
const tabWidth = (availableWidth - totalGapWidth) / tabCount;
// 인디케이터 위치 계산 (패딩 + 탭 너비 * 인덱스 + gap * 인덱스)
const indicatorPosition = padding + (tabWidth + gap) * activeIndex;
// CSS 변수로 위치 설정 (픽셀 단위)
$tabMenu[0].style.setProperty('--tab-indicator-position', `${indicatorPosition}px`);
// 인디케이터 너비도 동적으로 설정
$tabMenu[0].style.setProperty('--tab-indicator-width', `${tabWidth}px`);
},
/**
* 탭 전환 처리
* @param {jQuery} $clickedTab - 클릭된 탭 요소
*/
switchTab($clickedTab) {
const $tabMenu = $clickedTab.closest(CONFIG.SELECTORS.tabWrap);
const $allTabs = $tabMenu.find('li');
// 콘텐츠 컨테이너 찾기 (pop-contents 또는 contents-wrap)
const $contentContainer = $clickedTab.closest('.pop-contents, .contents-wrap');
const $allContents = $contentContainer.find('.tab-content');
// 모든 탭에서 on 클래스 제거
$allTabs.removeClass(CONFIG.CLASSES.on);
// 클릭된 탭에 on 클래스 추가
$clickedTab.addClass(CONFIG.CLASSES.on);
// 슬라이딩 인디케이터 위치 업데이트 (round 타입인 경우)
this.updateSliderPosition($tabMenu, $clickedTab);
// 모든 콘텐츠 숨기기
$allContents.removeClass(CONFIG.CLASSES.show);
// 해당하는 콘텐츠만 표시
const contentClass = this.getContentClass($clickedTab);
if (contentClass) {
const $targetContent = $contentContainer.find('.tab-content.' + contentClass);
if ($targetContent.length) {
$targetContent.addClass(CONFIG.CLASSES.show);
}
}
},
/**
* 탭 초기 상태 설정
*/
initTabState() {
// 모든 tab-menu 찾기
$(CONFIG.SELECTORS.tabWrap).each(function() {
const $tabMenu = $(this);
const $tabs = $tabMenu.find('li');
const $contentContainer = $tabMenu.closest('.pop-contents, .contents-wrap');
if ($tabs.length === 0 || !$contentContainer.length) return;
// 활성화된 탭 찾기
const $activeTab = $tabs.filter('.' + CONFIG.CLASSES.on);
if ($activeTab.length > 0) {
// 활성화된 탭이 있으면 해당 콘텐츠 표시
TabController.switchTab($activeTab);
} else {
// 활성화된 탭이 없으면 첫 번째 탭 활성화
const $firstTab = $tabs.first();
$firstTab.addClass(CONFIG.CLASSES.on);
TabController.switchTab($firstTab);
}
// 리사이즈 시 인디케이터 위치 재계산
if ($tabMenu.hasClass('round')) {
$(window).on('resize.tabSlider', function() {
const $active = $tabMenu.find('li.' + CONFIG.CLASSES.on);
if ($active.length) {
TabController.updateSliderPosition($tabMenu, $active);
}
});
}
});
},
init() {
// 모든 tab-menu의 탭 클릭 이벤트
$(document).on('click', CONFIG.SELECTORS.tabWrap + ' li', function(e) {
e.preventDefault();
TabController.switchTab($(this));
});
// 키보드 접근성 지원 (탭 + 엔터/스페이스)
$(document).on('keydown', CONFIG.SELECTORS.tabWrap + ' li', function(e) {
// Enter 또는 Space 키
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
TabController.switchTab($(this));
}
// 화살표 키로 탭 전환
const $tabMenu = $(this).closest(CONFIG.SELECTORS.tabWrap);
const $tabs = $tabMenu.find('li');
const currentIndex = $tabs.index(this);
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
e.preventDefault();
let targetIndex;
if (e.key === 'ArrowLeft') {
targetIndex = currentIndex > 0 ? currentIndex - 1 : $tabs.length - 1;
} else {
targetIndex = currentIndex < $tabs.length - 1 ? currentIndex + 1 : 0;
}
const $targetTab = $tabs.eq(targetIndex);
$targetTab.focus();
TabController.switchTab($targetTab);
}
});
// 초기 상태 설정
this.initTabState();
}
};
// ============================================
// Privacy Tab Controller (하위 호환성 유지)
// ============================================
const PrivacyTabController = {
switchTab: function($clickedTab) {
return TabController.switchTab.call(TabController, $clickedTab);
},
init: function() {
return TabController.init.call(TabController);
},
initTabState: function() {
return TabController.initTabState.call(TabController);
}
};
// ============================================
// Main Initialization
// ============================================
const PopupController = {
init() {
// DOM이 준비되면 초기화
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
this.start();
});
} else {
this.start();
}
},
start() {
PopupCloseController.init();
EmailDomainController.init();
TimerController.init();
FindAccountController.init();
LoginTabController.init();
TermsAgreementController.init();
JoinCompletionController.init();
TabController.init();
}
};
// ============================================
// Start Application
// ============================================
PopupController.init();
// ============================================
// Global Functions
// ============================================
// 전역에서 접근 가능하도록 함수 노출
window.PopupController = PopupController;
window.TabController = TabController;
window.PrivacyTabController = PrivacyTabController; // 하위 호환성 유지
})();