(() => {
const { pageHelper, EventManager, constants, utils } = ShopbySkin;
if (utils.isSignedIn()) {
if (!document.referrer || location.href === document.referrer) {
history.back();
return;
}
utils.moveTo({
url: document.referrer,
});
}
const signUpFormHelper = pageHelper.signUpFormHelper();
const containerEl = document.querySelector(`[shopby-helper-key="sign-up-form"]`);
const profileExtraInformationEl = document.querySelector('profile-extra-information');
signUpFormHelper.initialize({
helperKey: 'sign-up-form',
});
const flattenRequest = (helper) => {
const {
profileBasicInformation,
profileNicknameInformation,
profileEmailInformation,
profileSmsInformation,
profileCertification,
profileOptionalInformation,
termsInformation,
profileExtraInformation,
} = helper.getState();
const selectedCustomTerms = termsInformation?.customTerms.filter(({ checked }) => !!checked).map((term) => term.id);
return {
...profileBasicInformation,
...profileEmailInformation,
...profileSmsInformation,
...profileCertification,
...profileOptionalInformation,
extraInfo: profileExtraInformation?.extraInfoContents,
nickname: profileNicknameInformation?.nickname,
joinTermsAgreements: termsInformation?.terms,
customTermsNos: selectedCustomTerms,
};
};
const checkInvalidProfileForm = (request) => {
// eslint-disable-next-line complexity
const errors = Object.keys(utils.changeProfileFormVerifyMap)?.map((key) => {
const value = request?.[key]?.value ?? '';
switch (key) {
case 'memberId':
return {
...utils.changeProfileFormVerifyMap?.[key]({
value,
isDuplicated: request?.memberId?.isDuplicate,
isDormantMemberId: !!request?.isRecentlyExitedMember,
}),
field: key,
};
case 'passwordConfirm':
return {
...utils.changeProfileFormVerifyMap?.[key]({ value, comparisonValue: request?.password?.value }),
field: key,
};
case 'memberName':
return {
...utils.changeProfileFormVerifyMap?.[key]({ value, isRequired: request?.memberName?.isRequired }),
field: key,
};
case 'nickname':
return {
...utils.changeProfileFormVerifyMap?.[key]({
value,
isDuplicated: request?.nickname?.isDuplicate,
isRequired: request?.nickname?.isRequired,
}),
field: key,
};
case 'email':
return {
...utils.changeProfileFormVerifyMap?.[key]({
value,
isDuplicated: request?.email?.isDuplicate,
isRequired: request?.email?.isRequired,
}),
field: key,
};
case 'mobileNo':
case 'telephoneNo':
return {
...utils.changeProfileFormVerifyMap?.[key]({ value, isRequired: request?.[key]?.isRequired }),
field: key,
};
case 'detailAddress':
return {
...utils.changeProfileFormVerifyMap?.[key]({
value,
zipCode: request?.zipCd,
isRequired: request?.detailAddress?.isRequired,
}),
field: key,
};
case 'birthday':
case 'sex':
return {
...utils.changeProfileFormVerifyMap?.[key]({ value, isRequired: request?.[key]?.isRequired }),
field: key,
};
case 'extraInfo':
return { ...utils.changeProfileFormVerifyMap?.[key](request.extraInfo), field: key };
case 'joinTermsAgreements':
return { ...utils.changeProfileFormVerifyMap?.[key](request.joinTermsAgreements), field: key };
default:
return {
...utils.changeProfileFormVerifyMap?.[key]({ value, isRequired: request?.[key]?.isRequired }),
field: key,
};
}
});
return errors.filter((error) => !error.isValid);
};
// eslint-disable-next-line complexity
const checkCertificatedValidation = (request) => {
// ✅ KCP 본인인증 완료 시 SMS/이메일 검증 건너뛰기
if (kcpAuthManagerMobile.authDataStore.isAuthenticated) {
// 인증 유효 시간 체크 (30분)
const now = Date.now();
const authAge = now - kcpAuthManagerMobile.authDataStore.timestamp;
const THIRTY_MINUTES = 30 * 60 * 1000;
if (authAge > THIRTY_MINUTES) {
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'CAUTION',
message: '인증이 만료되었습니다. 휴대폰 본인인증을 다시 진행해주세요.',
});
return false;
}
return true; // KCP 인증 완료, 모든 인증 검증 건너뛰기
}
const invalidEmail = request?.emailCertificationStatus === 'INITIAL';
const invalidSmsInternalCertification = request?.smsCertificationStatus === 'INITIAL';
const invalidSmsExternalAuthentication = request?.smsCertificationStatus === 'SMS_AUTHENTICATION' && !request?.ci;
if (invalidEmail || invalidSmsInternalCertification || invalidSmsExternalAuthentication) {
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'CAUTION',
message: `${invalidEmail ? '이메일 인증을 진행해 주세요.' : '휴대폰 인증을 진행해 주세요.'}`,
onClose: () => {
utils.handleFocusEvent({ containerEl, fields: invalidEmail ? 'email' : 'mobileNo' });
},
});
return false;
}
const message = request.certificatedNumber?.length ? '인증을 진행해주세요.' : '인증번호를 입력해주세요.';
if (request.certificated && (!request.certificated.value || !request.certificated.isValid)) {
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'CAUTION',
message: `${request.certificated.message ?? message}`,
onClose: () => {
utils.handleFocusEvent({ containerEl, fields: 'certificatedNumber' });
},
});
return false;
}
return true;
};
const CLICK_EVENT_HANDLER_MAP = {
// eslint-disable-next-line complexity
EMAIL_CERTIFICATION: async (helper) => {
const { profileEmailInformation } = helper.getState();
const isInvalidEmail = profileEmailInformation?.email.value === '@' || !profileEmailInformation?.email.isValid;
if (isInvalidEmail) {
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'CAUTION',
message: `${profileEmailInformation?.email.message ?? '이메일을 입력해주세요.'}`,
});
return;
}
if (profileEmailInformation?.emailCertificationStatus === 'INITIAL') {
await helper.sendCertificationCode(profileEmailInformation.email.value, 'EMAIL');
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'SUCCESS',
message: '인증번호가 발송되었습니다.',
});
} else {
EventManager.fire('MODAL_CONFIRM_OPEN', {
noticeType: 'WARNING',
message: '인증번호를 재발송하시겠습니까?',
onConfirm: async () => {
await helper.sendCertificationCode(profileEmailInformation.email.value, 'EMAIL');
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'SUCCESS',
message: '인증번호가 발송되었습니다.',
});
},
});
}
},
// eslint-disable-next-line complexity
SMS_CERTIFICATION: async (helper) => {
const { profileSmsInformation } = helper.getState();
const isInvalidMobileNo = !profileSmsInformation?.mobileNo.value || !profileSmsInformation?.mobileNo.isValid;
if (isInvalidMobileNo) {
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'CAUTION',
message: `${profileSmsInformation?.mobileNo.message ?? '휴대폰 번호를 입력해주세요.'}`,
});
return;
}
if (profileSmsInformation?.smsCertificationStatus === 'INITIAL') {
await helper.sendCertificationCode(profileSmsInformation.mobileNo.value, 'SMS');
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'SUCCESS',
message: '인증번호가 발송되었습니다.',
});
} else {
EventManager.fire('MODAL_CONFIRM_OPEN', {
noticeType: 'WARNING',
message: '인증번호를 재발송하시겠습니까?',
onConfirm: async () => {
await helper.sendCertificationCode(profileSmsInformation.mobileNo.value, 'SMS');
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'SUCCESS',
message: '인증번호가 발송되었습니다.',
});
},
});
}
},
AUTHENTICATION_BY_PHONE: () => {
EventManager.fire('OPEN_LAYER_MODAL', {
name: 'kcp-sms-authentication',
data: { type: 'JOIN_TIME' },
onClose: ({ reason, state }) => {
if (reason === 'DID_SUBMIT') {
ShopbySkin.EventManager.fire('SUCCESS_AUTHENTICATION_SMS', state);
}
},
});
},
SEARCH_ZIP_CODE: () => {
EventManager.fire('OPEN_LAYER_MODAL', {
modalAddClass: 'search-zip-code full-modal',
name: 'page-zip-code',
onClose: ({ reason, state }) => {
if (reason === 'DID_SUBMIT') {
ShopbySkin.EventManager.fire('SELECT_ZIP_CODE', {
moduleKey: 'profile-optional-information',
state,
});
}
},
});
},
SHOW_TERM_DETAIL: ({ elTarget, helper }) => {
const { termsInformation } = helper.getState();
if (!termsInformation) {
return;
}
const mergedTerms = [...termsInformation.terms, ...termsInformation.customTerms];
const selectedTerm = mergedTerms.find((term) => elTarget.getAttribute('shopby-term-id') === term.id.toString());
EventManager.fire('OPEN_LAYER_MODAL', {
name: 'term-detail',
data: selectedTerm,
});
},
// eslint-disable-next-line complexity
SUBMIT: async (helper) => {
await profileExtraInformationEl?.refetchConfigMemberExtraInfo();
const flattedRequest = flattenRequest(helper);
const invalidRequest = checkInvalidProfileForm(flattedRequest);
if (invalidRequest?.length) {
const [error] = invalidRequest;
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'CAUTION',
message: `${error.message}`,
onClose: () => {
utils.handleFocusEvent({ containerEl, fields: error.field });
EventManager.fire('INVALID_PROFILE_FORM', {
data: invalidRequest,
});
},
});
return;
}
if (!checkCertificatedValidation(flattedRequest)) {
return;
}
const { data } = await helper.submit(flattedRequest);
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'SUCCESS',
message: '회원가입이 완료되었습니다.',
onClose: () => {
if (data.memberStatus === 'PENDING') {
utils.moveTo({
url: constants.PAGE.SIGN_UP_PENDING,
replace: true,
});
} else {
utils.moveTo({
url: constants.PAGE.SIGN_UP_COMPLETE,
replace: true,
});
}
},
});
},
HANDLE_DELETE_IMAGE_BTN_CLICK: (_, event) => {
event.preventDefault();
EventManager.fire('MODAL_CONFIRM_OPEN', {
noticeType: 'WARNING',
message: '첨부파일을 삭제하시겠습니까?',
onConfirm: () => {
const extraInfoNo = event.target.closest('[shopby-extra-info-no]').getAttribute('shopby-extra-info-no');
profileExtraInformationEl.deleteImage(extraInfoNo);
},
});
},
};
const clickEventListener = (event) => {
const { target } = event;
const action = target.getAttribute('shopby-action');
if (action === 'SHOW_TERM_DETAIL') {
CLICK_EVENT_HANDLER_MAP[action]?.({
helper: signUpFormHelper,
elTarget: target,
});
} else {
CLICK_EVENT_HANDLER_MAP[action]?.(signUpFormHelper, event);
}
};
// KCP 인증 및 성인 인증 시스템 (모바일)
const kcpAuthManagerMobile = {
authDataStore: {
timestamp: null,
originalData: null,
isAuthenticated: false
},
initialize: function() {
this.showKcpFields();
this.bindEvents();
},
showKcpFields: function() {
const kcpFieldsContainer = document.getElementById('kcp-auth-fields-mobile');
if (kcpFieldsContainer) {
kcpFieldsContainer.style.display = 'block';
}
},
formatPhoneNumber: function(phone) {
if (!phone) return '';
const cleaned = phone.replace(/\D/g, '');
if (cleaned.length === 11) {
return cleaned.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
}
return phone;
},
formatBirthday: function(birthday) {
if (!birthday) return '';
if (birthday.length === 8) {
return `${birthday.substr(0, 4)}-${birthday.substr(4, 2)}-${birthday.substr(6, 2)}`;
}
return birthday;
},
fillKcpAuthData: function(authData) {
// 데이터 저장
this.authDataStore.timestamp = Date.now();
this.authDataStore.originalData = { ...authData };
this.authDataStore.isAuthenticated = true;
// 데이터 포맷팅
const formattedPhone = this.formatPhoneNumber(authData.phone);
const formattedBirthday = this.formatBirthday(authData.birthday);
// UI 업데이트
this.updateUI({
name: authData.name,
phone: formattedPhone,
birthday: formattedBirthday,
mobile: formattedPhone
});
// 성인 인증 확인
const isAdultUser = adultVerificationManagerMobile.isAdult(formattedBirthday);
adultVerificationManagerMobile.updateSubmitButton(isAdultUser);
},
updateUI: function(data) {
const fields = [
{ id: 'kcp-member-name-mobile', value: data.name, desc: `이름: ${data.name}` },
{ id: 'kcp-telephone-no-mobile', value: data.phone, desc: `전화번호: ${data.phone}` },
{ id: 'kcp-birthday-mobile', value: data.birthday, desc: `생년월일: ${data.birthday}` },
{ id: 'kcp-mobile-no-mobile', value: data.mobile, desc: `휴대폰번호: ${data.mobile}` }
];
fields.forEach(field => {
const input = document.getElementById(field.id);
const container = input?.closest('.kcp-auth-field-custom');
const description = container?.querySelector('.description');
if (input && container && description) {
input.value = field.value;
description.textContent = field.desc;
container.classList.remove('not-authenticated');
container.classList.add('authenticated');
}
});
},
copyToShopbyFields: function() {
const fieldMappings = [
{
source: 'kcp-member-name-mobile',
target: 'div[slot="memberName"] input[name="memberName"]'
},
{
source: 'kcp-telephone-no-mobile',
target: 'div[slot="telephoneNo"] input[name="telephoneNo"]'
},
{
source: 'kcp-mobile-no-mobile',
target: 'div[slot="mobileNo"] input[name="telephoneNo"]'
}
];
fieldMappings.forEach(mapping => {
const sourceInput = document.getElementById(mapping.source);
const targetInput = document.querySelector(mapping.target);
if (sourceInput && targetInput && sourceInput.value) {
targetInput.value = sourceInput.value;
// 이벤트 발생으로 ShopBy 프레임워크에 알림
const inputEvent = new Event('input', { bubbles: true });
const changeEvent = new Event('change', { bubbles: true });
targetInput.dispatchEvent(inputEvent);
targetInput.dispatchEvent(changeEvent);
}
});
// 생년월일 특별 처리
this.copyBirthdayToShopby();
},
copyBirthdayToShopby: function() {
const birthdayInput = document.getElementById('kcp-birthday-mobile');
if (!birthdayInput || !birthdayInput.value) return;
const birthday = birthdayInput.value; // YYYY-MM-DD 형식
const [year, month, day] = birthday.split('-');
// ShopBy 생년월일 필드 찾기 및 설정
const yearSelect = document.querySelector('div[slot="birthday"] select:nth-child(1)');
const monthSelect = document.querySelector('div[slot="birthday"] select:nth-child(2)');
const daySelect = document.querySelector('div[slot="birthday"] select:nth-child(3)');
if (yearSelect) {
yearSelect.value = year;
yearSelect.dispatchEvent(new Event('change', { bubbles: true }));
}
if (monthSelect) {
monthSelect.value = month;
monthSelect.dispatchEvent(new Event('change', { bubbles: true }));
}
if (daySelect) {
daySelect.value = day;
daySelect.dispatchEvent(new Event('change', { bubbles: true }));
}
},
bindEvents: function() {
// SUCCESS_AUTHENTICATION_SMS 이벤트 리스너
EventManager.on('SUCCESS_AUTHENTICATION_SMS', (authData) => {
this.fillKcpAuthData(authData);
});
}
};
const adultVerificationManagerMobile = {
initialize: function() {
this.showInitialNotice();
},
isAdult: function(birthDate) {
if (!birthDate) return false;
const birth = new Date(birthDate);
const birthYear = birth.getFullYear();
const currentYear = new Date().getFullYear();
// 세는 나이 기준: 현재년도 - 출생년도 >= 20
const koreanAge = currentYear - birthYear + 1;
const adultBirthYear = currentYear - 19
// 2007년생부터는 미성년자 (2006년생까지만 성인)
return birthYear <= adultBirthYear && koreanAge >= 20;
},
updateSubmitButton: function(isAdult) {
const submitButtons = document.querySelectorAll('button[shopby-action="SUBMIT"]');
submitButtons.forEach(button => {
if (isAdult) {
button.classList.add('adult-verified');
} else {
button.classList.remove('adult-verified');
this.showAgeWarning();
}
});
},
showInitialNotice: function() {
const noticeDiv = document.createElement('div');
noticeDiv.className = 'adult-verification-notice';
noticeDiv.innerHTML = '성인 인증을 위해 휴대폰 본인인증을 진행해주세요.';
const submitSection = document.querySelector('.sign-up-form__confirm');
if (submitSection) {
submitSection.insertBefore(noticeDiv, submitSection.firstChild);
}
},
showAgeWarning: function() {
// 기존 경고 메시지 제거
const existingWarning = document.querySelector('.age-warning');
if (existingWarning) {
existingWarning.remove();
}
const warningDiv = document.createElement('div');
warningDiv.className = 'age-warning';
warningDiv.textContent = '⚠️ 20세 미만은 회원가입이 불가능합니다.';
const submitSection = document.querySelector('.sign-up-form__confirm');
if (submitSection) {
submitSection.insertBefore(warningDiv, submitSection.firstChild);
}
},
validateForSubmit: function() {
if (!kcpAuthManagerMobile.authDataStore.isAuthenticated) {
return {
isValid: false,
message: '휴대폰 본인인증을 통해 성인 인증을 완료해주세요.'
};
}
// 시간 만료 검사 (30분)
const currentTime = Date.now();
const authTime = kcpAuthManagerMobile.authDataStore.timestamp;
const thirtyMinutes = 30 * 60 * 1000;
if (currentTime - authTime > thirtyMinutes) {
return {
isValid: false,
message: '본인인증이 만료되었습니다. 다시 인증해주세요.'
};
}
// 나이 재검증
const originalData = kcpAuthManagerMobile.authDataStore.originalData;
if (!originalData || !originalData.birthday) {
return {
isValid: false,
message: '생년월일 정보가 올바르지 않습니다.'
};
}
const formattedBirthday = kcpAuthManagerMobile.formatBirthday(originalData.birthday);
if (!this.isAdult(formattedBirthday)) {
return {
isValid: false,
message: '20세 미만은 회원가입이 불가능합니다.'
};
}
// CSS 우회 공격 방지
const submitButtons = document.querySelectorAll('button[shopby-action="SUBMIT"]');
const hasAdultVerified = Array.from(submitButtons).some(btn =>
btn.classList.contains('adult-verified')
);
if (!hasAdultVerified) {
return {
isValid: false,
message: '성인 인증이 완료되지 않았습니다.'
};
}
return { isValid: true };
}
};
// 기존 SUBMIT 함수를 확장하여 성인 인증 검사 추가
const originalSubmit = CLICK_EVENT_HANDLER_MAP.SUBMIT;
CLICK_EVENT_HANDLER_MAP.SUBMIT = async (helper) => {
// 성인 인증 검사 (CSS 우회 방지)
const adultVerificationResult = adultVerificationManagerMobile.validateForSubmit();
if (!adultVerificationResult.isValid) {
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'CAUTION',
message: `${adultVerificationResult.message}`
});
return;
}
// KCP 데이터를 ShopBy 필드로 복사
kcpAuthManagerMobile.copyToShopbyFields();
await profileExtraInformationEl?.refetchConfigMemberExtraInfo();
const flattedRequest = flattenRequest(helper);
// ✅ KCP 인증 완료 시 ci 값 직접 주입
if (kcpAuthManagerMobile.authDataStore.isAuthenticated) {
const storedAuthData = kcpAuthManagerMobile.authDataStore.originalData;
if (storedAuthData && storedAuthData.ci) {
flattedRequest.ci = storedAuthData.ci;
}
}
const invalidRequest = checkInvalidProfileForm(flattedRequest);
if (invalidRequest?.length) {
const [error] = invalidRequest;
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'CAUTION',
message: `${error.message}`,
onClose: () => {
utils.handleFocusEvent({ containerEl, fields: error.field });
EventManager.fire('INVALID_PROFILE_FORM', {
data: invalidRequest,
});
},
});
return;
}
if (!checkCertificatedValidation(flattedRequest)) {
return;
}
const { data } = await helper.submit(flattedRequest);
EventManager.fire('MODAL_ALERT_OPEN', {
noticeType: 'SUCCESS',
message: '회원가입이 완료되었습니다.',
onClose: () => {
if (data.memberStatus === 'PENDING') {
utils.moveTo({
url: constants.PAGE.SIGN_UP_PENDING,
replace: true,
});
} else {
utils.moveTo({
url: constants.PAGE.SIGN_UP_COMPLETE,
replace: true,
});
}
},
});
};
// 페이지 로드 시 시스템 초기화
document.addEventListener('DOMContentLoaded', function() {
kcpAuthManagerMobile.initialize();
adultVerificationManagerMobile.initialize();
});
containerEl.addEventListener('click', clickEventListener);
})();