1228 lines
57 KiB
JavaScript
1228 lines
57 KiB
JavaScript
/* public/assets/js/mypage_renew.js */
|
||
(function () {
|
||
'use strict';
|
||
|
||
const CFG = window.mypageRenew || {};
|
||
const URLS = (CFG.urls || {});
|
||
|
||
const $ = (sel, root = document) => root.querySelector(sel);
|
||
|
||
function csrfToken() {
|
||
return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
|
||
}
|
||
|
||
function isMobileUA() {
|
||
const ua = navigator.userAgent || '';
|
||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
|
||
}
|
||
|
||
// -----------------------------
|
||
// 1) 재인증 타이머 (항상 실행)
|
||
// - view는 data-expire(unix ts), 기존은 data-until(ISO)
|
||
// - 둘 다 지원하도록 수정
|
||
// -----------------------------
|
||
(function reauthCountdown() {
|
||
const box = document.querySelector('.mypage-reauth--countdown');
|
||
const out = document.getElementById('reauthCountdown');
|
||
if (!box || !out) return;
|
||
|
||
const untilStr = (box.getAttribute('data-until') || '').trim();
|
||
const expireTs = parseInt((box.getAttribute('data-expire') || '0').trim(), 10);
|
||
const remainFallback = parseInt(box.getAttribute('data-remain') || '0', 10);
|
||
|
||
function parseUntilMsFromISO(s) {
|
||
if (!s) return null;
|
||
const isoLike = s.includes('T') ? s : s.replace(' ', 'T');
|
||
const ms = Date.parse(isoLike);
|
||
return Number.isFinite(ms) ? ms : null;
|
||
}
|
||
|
||
let untilMs = parseUntilMsFromISO(untilStr);
|
||
|
||
// ✅ data-expire가 있으면 우선 사용 (unix seconds)
|
||
if (!untilMs && Number.isFinite(expireTs) && expireTs > 0) {
|
||
untilMs = expireTs * 1000;
|
||
}
|
||
|
||
let remain = Math.max(0, Number.isFinite(remainFallback) ? remainFallback : 0);
|
||
let timer = null;
|
||
let expiredOnce = false;
|
||
|
||
function fmt(sec) {
|
||
sec = Math.max(0, sec | 0);
|
||
const m = Math.floor(sec / 60);
|
||
const s = sec % 60;
|
||
return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
|
||
}
|
||
|
||
function getRemainSec() {
|
||
if (untilMs !== null) {
|
||
const diffMs = untilMs - Date.now();
|
||
return Math.max(0, Math.floor(diffMs / 1000));
|
||
}
|
||
return Math.max(0, remain);
|
||
}
|
||
|
||
async function onExpiredOnce() {
|
||
if (expiredOnce) return;
|
||
expiredOnce = true;
|
||
|
||
await showMsg(
|
||
"인증 허용 시간이 만료되었습니다.\n\n보안을 위해 다시 재인증이 필요합니다.",
|
||
{ type: 'alert', title: '인증 만료' }
|
||
);
|
||
|
||
window.location.href = URLS.gateReset || '/mypage/info';
|
||
}
|
||
|
||
function tick() {
|
||
const sec = getRemainSec();
|
||
out.textContent = fmt(sec);
|
||
|
||
if (sec <= 0) {
|
||
if (timer) clearInterval(timer);
|
||
onExpiredOnce();
|
||
return;
|
||
}
|
||
if (untilMs === null) remain -= 1;
|
||
}
|
||
|
||
tick();
|
||
timer = setInterval(tick, 1000);
|
||
})();
|
||
|
||
// -------------------------------------------
|
||
// 2) PASS 연락처 변경 (기존 로직 유지)
|
||
// -------------------------------------------
|
||
(function phoneChange() {
|
||
const btn = document.querySelector('[data-action="phone-change"]');
|
||
if (!btn) return;
|
||
|
||
const readyUrl = btn.getAttribute('data-ready-url') || URLS.passReady;
|
||
if (!readyUrl) return;
|
||
|
||
function openIframeModal(popupName = 'danal_authtel_popup', w = 420, h = 750) {
|
||
const old = document.getElementById(popupName);
|
||
if (old) old.remove();
|
||
|
||
const wrap = document.createElement('div');
|
||
wrap.id = popupName;
|
||
|
||
wrap.innerHTML = `
|
||
<div class="danal-modal-dim" style="position:fixed;top:0;left:0;width:100%;height:100%;z-index:200000;background:#000;opacity:.55"></div>
|
||
<div class="danal-modal-box" style="position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:${w}px;height:${h}px;background:#fff;border-radius:12px;z-index:200001;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.35);">
|
||
<div style="height:46px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;background:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.08);">
|
||
<div style="font-weight:900;font-size:13px;color:#111;">PASS 본인인증</div>
|
||
<button type="button" id="${popupName}_close"
|
||
aria-label="인증창 닫기"
|
||
style="width:34px;height:34px;border-radius:10px;border:1px solid rgba(0,0,0,.12);background:#fff;cursor:pointer;font-size:18px;line-height:1;color:#111;">×</button>
|
||
</div>
|
||
<iframe id="${popupName}_iframe" name="${popupName}_iframe" style="width:100%;height:calc(100% - 46px);border:none"></iframe>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(wrap);
|
||
|
||
function removeModal() {
|
||
const el = document.getElementById(popupName);
|
||
if (el) el.remove();
|
||
}
|
||
|
||
async function askCancelAndGo() {
|
||
const ok = await showMsg(
|
||
"인증을 중단하시겠습니까?\n\n닫으면 현재 변경 진행이 취소됩니다.",
|
||
{
|
||
type: "confirm",
|
||
title: "인증취소",
|
||
closeOnBackdrop: false,
|
||
closeOnX: false,
|
||
closeOnEsc: false,
|
||
}
|
||
);
|
||
|
||
if (!ok) return;
|
||
|
||
const ifr = document.getElementById(popupName + "_iframe");
|
||
if (ifr) ifr.src = "about:blank";
|
||
|
||
removeModal();
|
||
}
|
||
|
||
const closeBtn = wrap.querySelector('#' + popupName + '_close');
|
||
if (closeBtn) closeBtn.addEventListener('click', askCancelAndGo);
|
||
|
||
window.closeIframe = function () {
|
||
removeModal();
|
||
};
|
||
|
||
return popupName + '_iframe';
|
||
}
|
||
|
||
function postToIframe(url, targetName, fieldsObj) {
|
||
const temp = document.createElement('form');
|
||
temp.method = 'POST';
|
||
temp.action = url;
|
||
temp.target = targetName;
|
||
|
||
const csrf = document.createElement('input');
|
||
csrf.type = 'hidden';
|
||
csrf.name = '_token';
|
||
csrf.value = csrfToken();
|
||
temp.appendChild(csrf);
|
||
|
||
const fields = document.createElement('input');
|
||
fields.type = 'hidden';
|
||
fields.name = 'fields';
|
||
fields.value = JSON.stringify(fieldsObj || {});
|
||
temp.appendChild(fields);
|
||
|
||
const plat = document.createElement('input');
|
||
plat.type = 'hidden';
|
||
plat.name = 'platform';
|
||
plat.value = isMobileUA() ? 'mobile' : 'web';
|
||
temp.appendChild(plat);
|
||
|
||
document.body.appendChild(temp);
|
||
temp.submit();
|
||
temp.remove();
|
||
}
|
||
|
||
window.addEventListener('message', async (ev) => {
|
||
const d = ev.data || {};
|
||
if (d.type !== 'danal_result') return;
|
||
|
||
if (typeof window.closeIframe === 'function') window.closeIframe();
|
||
|
||
await showMsg(
|
||
d.message || (d.ok ? '본인인증이 완료되었습니다.' : '본인인증에 실패했습니다.'),
|
||
{ type: 'alert', title: d.ok ? '인증 완료' : '인증 실패' }
|
||
);
|
||
|
||
if (d.redirect) window.location.href = d.redirect;
|
||
});
|
||
|
||
btn.addEventListener('click', async () => {
|
||
const ok = await showMsg(
|
||
`연락처를 변경하시겠습니까?
|
||
|
||
• PASS 본인인증은 가입자 본인 명의 휴대전화로만 가능합니다.
|
||
• 인증 정보가 기존 회원정보와 일치하지 않으면 변경할 수 없습니다.
|
||
|
||
계속 진행할까요?`,
|
||
{ type: 'confirm', title: '연락처 변경' }
|
||
);
|
||
if (!ok) return;
|
||
|
||
btn.disabled = true;
|
||
|
||
try {
|
||
const res = await fetch(readyUrl, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': csrfToken(),
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify({ purpose: 'mypage_phone_change' }),
|
||
});
|
||
|
||
const data = await res.json().catch(() => ({}));
|
||
|
||
if (!res.ok || data.ok === false) {
|
||
await showMsg(data.message || '본인인증 준비에 실패했습니다.', { type: 'alert', title: '오류' });
|
||
return;
|
||
}
|
||
|
||
if (data.reason === 'danal_ready' && data.popup && data.popup.url) {
|
||
const targetName = openIframeModal('danal_authtel_popup', 420, 750);
|
||
postToIframe(data.popup.url, targetName, data.popup.fields || {});
|
||
return;
|
||
}
|
||
|
||
await showMsg('ready 응답이 올바르지 않습니다. 서버 응답 형식을 확인해 주세요.', { type: 'alert', title: '오류' });
|
||
} catch (e) {
|
||
await showMsg('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.', { type: 'alert', title: '오류' });
|
||
} finally {
|
||
btn.disabled = false;
|
||
}
|
||
});
|
||
})();
|
||
|
||
// -------------------------------------------
|
||
// 공통: 모달 스타일/유틸
|
||
// -------------------------------------------
|
||
function ensureCommonModalStyle(styleId, cssText) {
|
||
if (document.getElementById(styleId)) return;
|
||
const st = document.createElement('style');
|
||
st.id = styleId;
|
||
st.textContent = cssText;
|
||
document.head.appendChild(st);
|
||
}
|
||
|
||
function makeErrorSetter(wrap, errSel) {
|
||
const errBox = $(errSel, wrap);
|
||
return function setError(message) {
|
||
if (!errBox) return;
|
||
const msg = (message || '').trim();
|
||
if (msg === '') {
|
||
errBox.style.display = 'none';
|
||
errBox.textContent = '';
|
||
return;
|
||
}
|
||
errBox.textContent = msg;
|
||
errBox.style.display = 'block';
|
||
};
|
||
}
|
||
|
||
// -------------------------------------------
|
||
// 3) 비밀번호 변경 레이어 팝업 (기존 유지 + 내부 에러)
|
||
// -------------------------------------------
|
||
(function passwordChange() {
|
||
const trigger = document.querySelector('[data-action="pw-change"]');
|
||
if (!trigger) return;
|
||
|
||
const postUrl = URLS.passwordUpdate;
|
||
if (!postUrl) {
|
||
trigger.addEventListener('click', async () => {
|
||
await showMsg('비밀번호 변경 URL이 설정되지 않았습니다.', { type: 'alert', title: '오류' });
|
||
});
|
||
return;
|
||
}
|
||
|
||
ensureCommonModalStyle('mypagePwModalStyle', `
|
||
.mypage-pwmodal{position:fixed;inset:0;z-index:220000;display:flex;align-items:center;justify-content:center}
|
||
.mypage-pwmodal__dim{position:absolute;inset:0;background:#000;opacity:.55}
|
||
.mypage-pwmodal__box{position:relative;width:min(420px,calc(100% - 28px));background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.35)}
|
||
.mypage-pwmodal__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.08)}
|
||
.mypage-pwmodal__ttl{font-weight:900;font-size:14px;color:#111}
|
||
.mypage-pwmodal__close{width:34px;height:34px;border-radius:10px;border:1px solid rgba(0,0,0,.12);background:#fff;cursor:pointer;font-size:18px;line-height:1;color:#111}
|
||
.mypage-pwmodal__bd{padding:14px}
|
||
.mypage-pwmodal__row{margin-top:10px}
|
||
.mypage-pwmodal__label{display:block;font-size:12px;font-weight:800;color:#667085;margin-bottom:6px}
|
||
.mypage-pwmodal__inp{width:100%;height:42px;border-radius:12px;border:1px solid #e5e7eb;padding:0 12px;font-size:14px;outline:none}
|
||
.mypage-pwmodal__hint{margin-top:10px;font-size:12px;color:#667085;line-height:1.4}
|
||
.mypage-pwmodal__error{margin-top:12px;padding:10px 12px;border-radius:12px;background:rgba(220,38,38,.08);border:1px solid rgba(220,38,38,.25);color:#b91c1c;font-weight:800;font-size:12px;display:none;white-space:pre-line}
|
||
.mypage-pwmodal__ft{display:flex;gap:10px;padding:12px 14px;border-top:1px solid rgba(0,0,0,.08);background:#fff}
|
||
.mypage-pwmodal__btn{flex:1;height:42px;border-radius:12px;border:1px solid rgba(0,0,0,.12);background:#fff;font-weight:900;cursor:pointer}
|
||
.mypage-pwmodal__btn--primary{border:none;background:#111;color:#fff}
|
||
.mypage-pwmodal__btn[disabled]{opacity:.6;cursor:not-allowed}
|
||
`);
|
||
|
||
function validateNewPw(pw) {
|
||
if (!pw) return '비밀번호를 입력해 주세요.';
|
||
if (pw.length < 8) return '비밀번호는 8자리 이상이어야 합니다.';
|
||
if (pw.length > 20) return '비밀번호는 20자리를 초과할 수 없습니다.';
|
||
const re = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,20}$/;
|
||
if (!re.test(pw)) return '비밀번호는 영문+숫자+특수문자를 포함해야 합니다.';
|
||
return '';
|
||
}
|
||
|
||
function openModal() {
|
||
const old = $('#mypagePwModal');
|
||
if (old) old.remove();
|
||
|
||
const wrap = document.createElement('div');
|
||
wrap.className = 'mypage-pwmodal';
|
||
wrap.id = 'mypagePwModal';
|
||
|
||
wrap.innerHTML = `
|
||
<div class="mypage-pwmodal__dim"></div>
|
||
<div class="mypage-pwmodal__box" role="dialog" aria-modal="true" aria-labelledby="mypagePwModalTitle">
|
||
<div class="mypage-pwmodal__hd">
|
||
<div class="mypage-pwmodal__ttl" id="mypagePwModalTitle">비밀번호 변경</div>
|
||
<button type="button" class="mypage-pwmodal__close" aria-label="닫기">×</button>
|
||
</div>
|
||
<div class="mypage-pwmodal__bd">
|
||
<div class="mypage-pwmodal__row">
|
||
<label class="mypage-pwmodal__label">현재 비밀번호</label>
|
||
<input type="password" class="mypage-pwmodal__inp" id="pw_current" autocomplete="current-password" />
|
||
</div>
|
||
<div class="mypage-pwmodal__row">
|
||
<label class="mypage-pwmodal__label">변경할 비밀번호</label>
|
||
<input type="password" class="mypage-pwmodal__inp" id="pw_new" autocomplete="new-password" />
|
||
</div>
|
||
<div class="mypage-pwmodal__row">
|
||
<label class="mypage-pwmodal__label">변경할 비밀번호 확인</label>
|
||
<input type="password" class="mypage-pwmodal__inp" id="pw_new2" autocomplete="new-password" />
|
||
</div>
|
||
|
||
<div class="mypage-pwmodal__hint">
|
||
• 8~20자<br/>
|
||
• 영문 + 숫자 + 특수문자 포함
|
||
</div>
|
||
|
||
<div class="mypage-pwmodal__error" id="pw_error"></div>
|
||
</div>
|
||
<div class="mypage-pwmodal__ft">
|
||
<button type="button" class="mypage-pwmodal__btn" data-act="cancel">취소</button>
|
||
<button type="button" class="mypage-pwmodal__btn mypage-pwmodal__btn--primary" data-act="submit">변경</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(wrap);
|
||
|
||
const setError = makeErrorSetter(wrap, '#pw_error');
|
||
|
||
function close() { wrap.remove(); }
|
||
|
||
// ✅ 닫기: X / 취소만
|
||
wrap.querySelector('.mypage-pwmodal__close')?.addEventListener('click', close);
|
||
wrap.querySelector('[data-act="cancel"]')?.addEventListener('click', close);
|
||
|
||
setTimeout(() => $('#pw_current', wrap)?.focus(), 10);
|
||
|
||
async function submit() {
|
||
setError('');
|
||
|
||
const cur = ($('#pw_current', wrap)?.value || '').trim();
|
||
const pw1 = ($('#pw_new', wrap)?.value || '').trim();
|
||
const pw2 = ($('#pw_new2', wrap)?.value || '').trim();
|
||
|
||
if (!cur) { setError('현재 비밀번호를 입력해 주세요.'); $('#pw_current', wrap)?.focus(); return; }
|
||
|
||
const err = validateNewPw(pw1);
|
||
if (err) { setError(err); $('#pw_new', wrap)?.focus(); return; }
|
||
|
||
if (pw1 !== pw2) { setError('변경할 비밀번호 확인이 일치하지 않습니다.'); $('#pw_new2', wrap)?.focus(); return; }
|
||
|
||
const ok = await showMsg('비밀번호를 변경하시겠습니까?', { type: 'confirm', title: '비밀번호 변경' });
|
||
if (!ok) return;
|
||
|
||
const btn = wrap.querySelector('[data-act="submit"]');
|
||
if (btn) btn.disabled = true;
|
||
|
||
try {
|
||
const res = await fetch(postUrl, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': csrfToken(),
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
current_password: cur,
|
||
password: pw1,
|
||
password_confirmation: pw2,
|
||
}),
|
||
});
|
||
|
||
const data = await res.json().catch(() => ({}));
|
||
|
||
if (res.status === 401 && data.redirect) {
|
||
await showMsg(data.message || '인증이 필요합니다.', { type: 'alert', title: '인증 필요' });
|
||
window.location.href = data.redirect;
|
||
return;
|
||
}
|
||
|
||
if (!res.ok || data.ok === false) {
|
||
const msg =
|
||
(data && data.message) ||
|
||
(data && data.errors && (data.errors.password?.[0] || data.errors.current_password?.[0])) ||
|
||
'비밀번호 변경에 실패했습니다.';
|
||
setError(msg);
|
||
return;
|
||
}
|
||
|
||
await showMsg(data.message || '비밀번호가 변경되었습니다.', { type: 'alert', title: '완료' });
|
||
close();
|
||
} catch (e) {
|
||
setError('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.');
|
||
} finally {
|
||
if (btn) btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
wrap.querySelector('[data-act="submit"]')?.addEventListener('click', submit);
|
||
|
||
wrap.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
submit();
|
||
}
|
||
});
|
||
}
|
||
|
||
trigger.addEventListener('click', openModal);
|
||
})();
|
||
|
||
// -------------------------------------------
|
||
// 4) 2차 비밀번호 변경 레이어 팝업 (신규)
|
||
// - 로그인 비밀번호 + 현재 2차비번 둘 다 검증 후 변경
|
||
// - 에러는 내부 붉은 박스
|
||
// - 닫힘: 취소 / X만
|
||
// -------------------------------------------
|
||
(function pin2Change() {
|
||
const trigger = document.querySelector('[data-action="pw2-change"]');
|
||
if (!trigger) return;
|
||
|
||
const postUrl = URLS.pin2Update; // Blade에서 주입 필요
|
||
if (!postUrl) {
|
||
trigger.addEventListener('click', async () => {
|
||
await showMsg('2차 비밀번호 변경 URL이 설정되지 않았습니다.', { type: 'alert', title: '오류' });
|
||
});
|
||
return;
|
||
}
|
||
|
||
ensureCommonModalStyle('mypagePin2ModalStyle', `
|
||
.mypage-pin2modal{position:fixed;inset:0;z-index:220000;display:flex;align-items:center;justify-content:center}
|
||
.mypage-pin2modal__dim{position:absolute;inset:0;background:#000;opacity:.55}
|
||
.mypage-pin2modal__box{position:relative;width:min(420px,calc(100% - 28px));background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.35)}
|
||
.mypage-pin2modal__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.08)}
|
||
.mypage-pin2modal__ttl{font-weight:900;font-size:14px;color:#111}
|
||
.mypage-pin2modal__close{width:34px;height:34px;border-radius:10px;border:1px solid rgba(0,0,0,.12);background:#fff;cursor:pointer;font-size:18px;line-height:1;color:#111}
|
||
.mypage-pin2modal__bd{padding:14px}
|
||
.mypage-pin2modal__row{margin-top:10px}
|
||
.mypage-pin2modal__label{display:block;font-size:12px;font-weight:800;color:#667085;margin-bottom:6px}
|
||
.mypage-pin2modal__inp{width:100%;height:42px;border-radius:12px;border:1px solid #e5e7eb;padding:0 12px;font-size:14px;outline:none}
|
||
.mypage-pin2modal__inp--pin{letter-spacing:6px;text-align:center;font-weight:900}
|
||
.mypage-pin2modal__hint{margin-top:10px;font-size:12px;color:#667085;line-height:1.4}
|
||
.mypage-pin2modal__error{margin-top:12px;padding:10px 12px;border-radius:12px;background:rgba(220,38,38,.08);border:1px solid rgba(220,38,38,.25);color:#b91c1c;font-weight:800;font-size:12px;display:none;white-space:pre-line}
|
||
.mypage-pin2modal__ft{display:flex;gap:10px;padding:12px 14px;border-top:1px solid rgba(0,0,0,.08);background:#fff}
|
||
.mypage-pin2modal__btn{flex:1;height:42px;border-radius:12px;border:1px solid rgba(0,0,0,.12);background:#fff;font-weight:900;cursor:pointer}
|
||
.mypage-pin2modal__btn--primary{border:none;background:#111;color:#fff}
|
||
.mypage-pin2modal__btn[disabled]{opacity:.6;cursor:not-allowed}
|
||
`);
|
||
|
||
function isPin4(v) {
|
||
return /^\d{4}$/.test(v || '');
|
||
}
|
||
|
||
function openModal() {
|
||
const old = $('#mypagePin2Modal');
|
||
if (old) old.remove();
|
||
|
||
const wrap = document.createElement('div');
|
||
wrap.className = 'mypage-pin2modal';
|
||
wrap.id = 'mypagePin2Modal';
|
||
|
||
wrap.innerHTML = `
|
||
<div class="mypage-pin2modal__dim"></div>
|
||
<div class="mypage-pin2modal__box" role="dialog" aria-modal="true" aria-labelledby="mypagePin2ModalTitle">
|
||
<div class="mypage-pin2modal__hd">
|
||
<div class="mypage-pin2modal__ttl" id="mypagePin2ModalTitle">2차 비밀번호 변경</div>
|
||
<button type="button" class="mypage-pin2modal__close" aria-label="닫기">×</button>
|
||
</div>
|
||
<div class="mypage-pin2modal__bd">
|
||
<div class="mypage-pin2modal__row">
|
||
<label class="mypage-pin2modal__label">현재 로그인 비밀번호</label>
|
||
<input type="password" class="mypage-pin2modal__inp" id="pin2_current_password" autocomplete="current-password" />
|
||
</div>
|
||
|
||
<div class="mypage-pin2modal__row">
|
||
<label class="mypage-pin2modal__label">이전 2차 비밀번호 (숫자 4자리)</label>
|
||
<input type="password" inputmode="numeric" maxlength="4" class="mypage-pin2modal__inp mypage-pin2modal__inp--pin" id="pin2_current" autocomplete="off" />
|
||
</div>
|
||
|
||
<div class="mypage-pin2modal__row">
|
||
<label class="mypage-pin2modal__label">새 2차 비밀번호 (숫자 4자리)</label>
|
||
<input type="password" inputmode="numeric" maxlength="4" class="mypage-pin2modal__inp mypage-pin2modal__inp--pin" id="pin2_new" autocomplete="off" />
|
||
</div>
|
||
|
||
<div class="mypage-pin2modal__row">
|
||
<label class="mypage-pin2modal__label">새 2차 비밀번호 확인</label>
|
||
<input type="password" inputmode="numeric" maxlength="4" class="mypage-pin2modal__inp mypage-pin2modal__inp--pin" id="pin2_new2" autocomplete="off" />
|
||
</div>
|
||
|
||
<div class="mypage-pin2modal__hint">
|
||
• 보안을 위해 <b>로그인 비밀번호</b>와 <b>이전 2차 비밀번호</b>를 모두 확인합니다.<br/>
|
||
• 2차 비밀번호는 <b>숫자 4자리</b>만 가능합니다.
|
||
</div>
|
||
|
||
<div class="mypage-pin2modal__error" id="pin2_error"></div>
|
||
</div>
|
||
|
||
<div class="mypage-pin2modal__ft">
|
||
<button type="button" class="mypage-pin2modal__btn" data-act="cancel">취소</button>
|
||
<button type="button" class="mypage-pin2modal__btn mypage-pin2modal__btn--primary" data-act="submit">변경</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(wrap);
|
||
|
||
const setError = makeErrorSetter(wrap, '#pin2_error');
|
||
|
||
function close() { wrap.remove(); }
|
||
|
||
// ✅ 닫기: X / 취소만
|
||
wrap.querySelector('.mypage-pin2modal__close')?.addEventListener('click', close);
|
||
wrap.querySelector('[data-act="cancel"]')?.addEventListener('click', close);
|
||
|
||
setTimeout(() => $('#pin2_current_password', wrap)?.focus(), 10);
|
||
|
||
// 숫자만 입력 보정(편의) — 필요 없으면 빼도 됨
|
||
function onlyDigitsMax4(el) {
|
||
if (!el) return;
|
||
el.addEventListener('input', () => {
|
||
el.value = (el.value || '').replace(/[^\d]/g, '').slice(0, 4);
|
||
});
|
||
}
|
||
onlyDigitsMax4($('#pin2_current', wrap));
|
||
onlyDigitsMax4($('#pin2_new', wrap));
|
||
onlyDigitsMax4($('#pin2_new2', wrap));
|
||
|
||
async function submit() {
|
||
setError('');
|
||
|
||
const curPw = ($('#pin2_current_password', wrap)?.value || '').trim();
|
||
const curPin2 = ($('#pin2_current', wrap)?.value || '').trim();
|
||
const pin2 = ($('#pin2_new', wrap)?.value || '').trim();
|
||
const pin2c = ($('#pin2_new2', wrap)?.value || '').trim();
|
||
|
||
if (!curPw) { setError('이전 비밀번호를 입력해 주세요.'); $('#pin2_current_password', wrap)?.focus(); return; }
|
||
if (!isPin4(curPin2)) { setError('이전 2차 비밀번호는 숫자 4자리여야 합니다.'); $('#pin2_current', wrap)?.focus(); return; }
|
||
if (!isPin4(pin2)) { setError('2차 비밀번호는 숫자 4자리여야 합니다.'); $('#pin2_new', wrap)?.focus(); return; }
|
||
if (pin2 !== pin2c) { setError('2차 비밀번호 확인이 일치하지 않습니다.'); $('#pin2_new2', wrap)?.focus(); return; }
|
||
|
||
const ok = await showMsg('2차 비밀번호를 변경하시겠습니까?', { type: 'confirm', title: '2차 비밀번호 변경' });
|
||
if (!ok) return;
|
||
|
||
const btn = wrap.querySelector('[data-act="submit"]');
|
||
if (btn) btn.disabled = true;
|
||
|
||
try {
|
||
const res = await fetch(postUrl, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': csrfToken(),
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
current_password: curPw,
|
||
current_pin2: curPin2,
|
||
pin2: pin2,
|
||
pin2_confirmation: pin2c,
|
||
}),
|
||
});
|
||
|
||
const data = await res.json().catch(() => ({}));
|
||
|
||
if (res.status === 401 && data.redirect) {
|
||
await showMsg(data.message || '인증이 필요합니다.', { type: 'alert', title: '인증 필요' });
|
||
window.location.href = data.redirect;
|
||
return;
|
||
}
|
||
|
||
if (!res.ok || data.ok === false) {
|
||
// 422 validate or mismatch
|
||
const msg =
|
||
(data && data.message) ||
|
||
(data && data.errors && (
|
||
data.errors.current_password?.[0] ||
|
||
data.errors.current_pin2?.[0] ||
|
||
data.errors.pin2?.[0] ||
|
||
data.errors.pin2_confirmation?.[0]
|
||
)) ||
|
||
'2차 비밀번호 변경에 실패했습니다.';
|
||
|
||
setError(msg);
|
||
return;
|
||
}
|
||
|
||
await showMsg(data.message || '2차 비밀번호가 변경되었습니다.', { type: 'alert', title: '완료' });
|
||
close();
|
||
} catch (e) {
|
||
setError('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.');
|
||
} finally {
|
||
if (btn) btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
wrap.querySelector('[data-act="submit"]')?.addEventListener('click', submit);
|
||
|
||
wrap.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
submit();
|
||
}
|
||
});
|
||
}
|
||
|
||
trigger.addEventListener('click', openModal);
|
||
})();
|
||
|
||
// -------------------------------------------
|
||
// X) 출금계좌 등록/수정 (Dozn 성명인증 + 저장)
|
||
// - 입력: 2차 비밀번호(4자리), 금융권, 은행, 계좌번호(숫자만), 예금주
|
||
// - showMsg confirm/alert 사용
|
||
// - 에러는 모달 내부 붉은 글씨
|
||
// - 닫기: X / 취소만 (dim 클릭/ESC 닫기 없음)
|
||
// -------------------------------------------
|
||
(function withdrawAccount() {
|
||
const trigger = document.querySelector('[data-action="withdraw-account"]');
|
||
if (!trigger) return;
|
||
|
||
const postUrl = URLS.withdrawVerifyOut;
|
||
const defaultDepositor = (CFG.memberName || '').trim();
|
||
|
||
// ✅ bankGroups는 config(bank_code.php)의 groups 구조(label/items)로 전달된다고 가정
|
||
const BANK_GROUPS = (CFG.bankGroups || {});
|
||
|
||
if (!postUrl) {
|
||
trigger.addEventListener('click', async () => {
|
||
await showMsg('출금계좌 인증 URL이 설정되지 않았습니다.', { type: 'alert', title: '오류' });
|
||
});
|
||
return;
|
||
}
|
||
|
||
ensureCommonModalStyle('mypageWithdrawModalStyle', `
|
||
.mypage-withmodal{position:fixed;inset:0;z-index:220000;display:flex;align-items:center;justify-content:center}
|
||
.mypage-withmodal__dim{position:absolute;inset:0;background:#000;opacity:.55}
|
||
.mypage-withmodal__box{position:relative;width:min(460px,calc(100% - 28px));background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.35)}
|
||
.mypage-withmodal__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.08)}
|
||
.mypage-withmodal__ttl{font-weight:900;font-size:14px;color:#111}
|
||
.mypage-withmodal__close{width:34px;height:34px;border-radius:10px;border:1px solid rgba(0,0,0,.12);background:#fff;cursor:pointer;font-size:18px;line-height:1;color:#111}
|
||
.mypage-withmodal__bd{padding:14px}
|
||
.mypage-withmodal__row{margin-top:10px}
|
||
.mypage-withmodal__label{display:block;font-size:12px;font-weight:800;color:#667085;margin-bottom:6px}
|
||
.mypage-withmodal__inp{width:100%;height:42px;border-radius:12px;border:1px solid #e5e7eb;padding:0 12px;font-size:14px;outline:none}
|
||
.mypage-withmodal__inp--pin{letter-spacing:6px;text-align:center;font-weight:900}
|
||
.mypage-withmodal__select{width:100%;height:42px;border-radius:12px;border:1px solid #e5e7eb;padding:0 10px;font-size:14px;outline:none;background:#fff}
|
||
.mypage-withmodal__hint{margin-top:10px;font-size:12px;color:#667085;line-height:1.4}
|
||
.mypage-withmodal__error{margin-top:12px;padding:10px 12px;border-radius:12px;background:rgba(220,38,38,.08);border:1px solid rgba(220,38,38,.25);color:#b91c1c;font-weight:800;font-size:12px;display:none;white-space:pre-line}
|
||
.mypage-withmodal__ft{display:flex;gap:10px;padding:12px 14px;border-top:1px solid rgba(0,0,0,.08);background:#fff}
|
||
.mypage-withmodal__btn{flex:1;height:42px;border-radius:12px;border:1px solid rgba(0,0,0,.12);background:#fff;font-weight:900;cursor:pointer}
|
||
.mypage-withmodal__btn--primary{border:none;background:#111;color:#fff}
|
||
.mypage-withmodal__btn[disabled]{opacity:.6;cursor:not-allowed}
|
||
`);
|
||
|
||
function isPin4(v){ return /^\d{4}$/.test(v || ''); }
|
||
function isDigits(v){ return /^\d+$/.test(v || ''); }
|
||
function isBankCode3(v){ return /^\d{3}$/.test(v || ''); }
|
||
|
||
function escapeHtml(s) {
|
||
return String(s ?? '').replace(/[&<>"']/g, m => ({
|
||
'&':'&','<':'<','>':'>','"':'"',"'":'''
|
||
}[m]));
|
||
}
|
||
|
||
function maskAccount(v) {
|
||
const s = String(v || '');
|
||
if (s.length <= 4) return s;
|
||
return '****' + s.slice(-4);
|
||
}
|
||
|
||
function groupOptionsHtml() {
|
||
const keys = Object.keys(BANK_GROUPS || {});
|
||
const opts = ['<option value="">금융권 선택</option>'];
|
||
for (const k of keys) {
|
||
const label = BANK_GROUPS[k]?.label || k;
|
||
opts.push(`<option value="${escapeHtml(k)}">${escapeHtml(label)}</option>`);
|
||
}
|
||
return opts.join('');
|
||
}
|
||
|
||
function bankOptionsByGroupHtml(groupKey) {
|
||
const group = BANK_GROUPS[groupKey];
|
||
const items = group?.items || {};
|
||
const codes = Object.keys(items);
|
||
|
||
if (!groupKey || !group || codes.length === 0) {
|
||
return '<option value="">금융권을 먼저 선택해 주세요</option>';
|
||
}
|
||
|
||
// 코드 오름차순
|
||
codes.sort();
|
||
const opts = ['<option value="">은행 선택</option>'];
|
||
for (const code of codes) {
|
||
const name = items[code];
|
||
opts.push(`<option value="${escapeHtml(code)}">${escapeHtml(code)} - ${escapeHtml(name)}</option>`);
|
||
}
|
||
return opts.join('');
|
||
}
|
||
|
||
function getBankName(groupKey, bankCode) {
|
||
return (BANK_GROUPS[groupKey]?.items && BANK_GROUPS[groupKey].items[bankCode]) ? BANK_GROUPS[groupKey].items[bankCode] : '';
|
||
}
|
||
|
||
function openModal() {
|
||
const old = document.getElementById('mypageWithdrawModal');
|
||
if (old) old.remove();
|
||
|
||
const isEdit = trigger.getAttribute('aria-label')?.includes('수정');
|
||
|
||
const wrap = document.createElement('div');
|
||
wrap.className = 'mypage-withmodal';
|
||
wrap.id = 'mypageWithdrawModal';
|
||
|
||
wrap.innerHTML = `
|
||
<div class="mypage-withmodal__dim"></div>
|
||
<div class="mypage-withmodal__box" role="dialog" aria-modal="true" aria-labelledby="mypageWithdrawModalTitle">
|
||
<div class="mypage-withmodal__hd">
|
||
<div class="mypage-withmodal__ttl" id="mypageWithdrawModalTitle">출금계좌 ${isEdit ? '수정' : '등록'}</div>
|
||
<button type="button" class="mypage-withmodal__close" aria-label="닫기">×</button>
|
||
</div>
|
||
|
||
<div class="mypage-withmodal__bd">
|
||
<div class="mypage-withmodal__row">
|
||
<label class="mypage-withmodal__label">2차 비밀번호 (숫자 4자리)</label>
|
||
<input type="password" inputmode="numeric" maxlength="4"
|
||
class="mypage-withmodal__inp mypage-withmodal__inp--pin"
|
||
id="with_pin2" autocomplete="off" />
|
||
</div>
|
||
|
||
<div class="mypage-withmodal__row">
|
||
<label class="mypage-withmodal__label">금융권</label>
|
||
<select class="mypage-withmodal__select" id="with_group">
|
||
${groupOptionsHtml()}
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mypage-withmodal__row">
|
||
<label class="mypage-withmodal__label">은행</label>
|
||
<select class="mypage-withmodal__select" id="with_bank">
|
||
<option value="">금융권을 먼저 선택해 주세요</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="mypage-withmodal__row">
|
||
<label class="mypage-withmodal__label">계좌번호 (숫자만)</label>
|
||
<input type="text" inputmode="numeric"
|
||
class="mypage-withmodal__inp" id="with_account"
|
||
placeholder="예) 1234567890123" autocomplete="off" />
|
||
</div>
|
||
|
||
<div class="mypage-withmodal__row">
|
||
<label class="mypage-withmodal__label">예금주(성명변경불가)</label>
|
||
<input type="text" class="mypage-withmodal__inp" id="with_depositor" value="${escapeHtml(defaultDepositor)}" readonly />
|
||
</div>
|
||
|
||
<div class="mypage-withmodal__hint">
|
||
• 보안을 위해 <b>2차 비밀번호</b> 확인 후 진행합니다.<br/>
|
||
• 예금주는 <b>회원 실명</b>과 동일해야 합니다.<br/>
|
||
• 계좌 성명 인증 완료 시, 출금계좌가 저장됩니다.
|
||
</div>
|
||
|
||
<div class="mypage-withmodal__error" id="with_error"></div>
|
||
</div>
|
||
|
||
<div class="mypage-withmodal__ft">
|
||
<button type="button" class="mypage-withmodal__btn" data-act="cancel">취소</button>
|
||
<button type="button" class="mypage-withmodal__btn mypage-withmodal__btn--primary" data-act="submit">인증/저장</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(wrap);
|
||
|
||
const setError = makeErrorSetter(wrap, '#with_error');
|
||
const close = () => wrap.remove();
|
||
|
||
// ✅ 닫기: X / 취소만
|
||
wrap.querySelector('.mypage-withmodal__close')?.addEventListener('click', close);
|
||
wrap.querySelector('[data-act="cancel"]')?.addEventListener('click', close);
|
||
|
||
const pinEl = document.getElementById('with_pin2');
|
||
const groupEl = document.getElementById('with_group');
|
||
const bankEl = document.getElementById('with_bank');
|
||
const accEl = document.getElementById('with_account');
|
||
const depEl = document.getElementById('with_depositor');
|
||
|
||
// 숫자만 입력 보정
|
||
if (pinEl) pinEl.addEventListener('input', () => {
|
||
pinEl.value = (pinEl.value || '').replace(/[^\d]/g, '').slice(0, 4);
|
||
});
|
||
if (accEl) accEl.addEventListener('input', () => {
|
||
accEl.value = (accEl.value || '').replace(/[^\d]/g, '');
|
||
});
|
||
|
||
// ✅ 금융권 선택 → 은행 목록 갱신
|
||
groupEl?.addEventListener('change', () => {
|
||
const g = (groupEl.value || '').trim();
|
||
bankEl.innerHTML = bankOptionsByGroupHtml(g);
|
||
bankEl.value = '';
|
||
});
|
||
|
||
setTimeout(() => pinEl?.focus(), 10);
|
||
|
||
async function submit() {
|
||
setError('');
|
||
|
||
const pin2 = (pinEl?.value || '').trim();
|
||
const groupKey = (groupEl?.value || '').trim();
|
||
const bankCode = (bankEl?.value || '').trim();
|
||
const account = (accEl?.value || '').trim();
|
||
const depositor = (depEl?.value || '').trim();
|
||
|
||
if (!isPin4(pin2)) { setError('2차 비밀번호는 숫자 4자리여야 합니다.'); pinEl?.focus(); return; }
|
||
if (!groupKey || !BANK_GROUPS[groupKey]) { setError('금융권을 선택해 주세요.'); groupEl?.focus(); return; }
|
||
if (!isBankCode3(bankCode)) { setError('은행을 선택해 주세요.'); bankEl?.focus(); return; }
|
||
|
||
// ✅ 선택한 금융권 안에 실제로 존재하는 은행인지 (클라 방어)
|
||
const bankName = getBankName(groupKey, bankCode);
|
||
if (!bankName) { setError('선택한 은행 정보가 올바르지 않습니다. 다시 선택해 주세요.'); bankEl?.focus(); return; }
|
||
|
||
if (!account || !isDigits(account)) { setError('계좌번호는 숫자만 입력해 주세요.'); accEl?.focus(); return; }
|
||
if (!depositor) { setError('예금주(성명)를 입력해 주세요.'); depEl?.focus(); return; }
|
||
|
||
const ok = await showMsg(
|
||
`출금계좌를 인증 후 저장하시겠습니까?\n\n금융권: ${BANK_GROUPS[groupKey]?.label || groupKey}\n은행: ${bankName} (${bankCode})\n계좌: ${maskAccount(account)}\n예금주: ${depositor}`,
|
||
{ type: 'confirm', title: '출금계좌 인증/저장' }
|
||
);
|
||
if (!ok) return;
|
||
|
||
const btn = wrap.querySelector('[data-act="submit"]');
|
||
if (btn) btn.disabled = true;
|
||
|
||
try {
|
||
const res = await fetch(postUrl, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': csrfToken(),
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
pin2: pin2,
|
||
bank_code: bankCode,
|
||
account: account,
|
||
depositor: depositor,
|
||
// groupKey는 서버 필수는 아니지만, 디버깅/검증 강화용으로 보내도 됨
|
||
// bank_group: groupKey,
|
||
}),
|
||
});
|
||
|
||
const data = await res.json().catch(() => ({}));
|
||
|
||
if (res.status === 401 && data.redirect) {
|
||
await showMsg(data.message || '인증이 필요합니다.', { type: 'alert', title: '인증 필요' });
|
||
window.location.href = data.redirect;
|
||
return;
|
||
}
|
||
|
||
if (!res.ok || data.ok === false) {
|
||
const msg =
|
||
(data && data.message) ||
|
||
(data && data.errors && (
|
||
data.errors.pin2?.[0] ||
|
||
data.errors.bank_code?.[0] ||
|
||
data.errors.account?.[0] ||
|
||
data.errors.depositor?.[0]
|
||
)) ||
|
||
'계좌 인증/저장에 실패했습니다.';
|
||
|
||
setError(msg);
|
||
return;
|
||
}
|
||
|
||
await showMsg(data.message || '인증완료 및 계좌번호가 등록되었습니다.', { type: 'alert', title: '완료' });
|
||
close();
|
||
window.location.reload();
|
||
} catch (e) {
|
||
setError('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.');
|
||
} finally {
|
||
if (btn) btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
wrap.querySelector('[data-act="submit"]')?.addEventListener('click', submit);
|
||
wrap.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
submit();
|
||
}
|
||
});
|
||
}
|
||
|
||
trigger.addEventListener('click', openModal);
|
||
|
||
})();
|
||
|
||
(function () {
|
||
'use strict';
|
||
|
||
const CFG = window.mypageRenew || {};
|
||
const URLS = CFG.urls || {};
|
||
|
||
const $ = (sel, root = document) => root.querySelector(sel);
|
||
|
||
function csrfToken() {
|
||
return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
|
||
}
|
||
|
||
function ensureStyle(styleId, cssText) {
|
||
if (document.getElementById(styleId)) return;
|
||
const st = document.createElement('style');
|
||
st.id = styleId;
|
||
st.textContent = cssText;
|
||
document.head.appendChild(st);
|
||
}
|
||
|
||
function makeErrorSetter(wrap, sel) {
|
||
const box = $(sel, wrap);
|
||
return function setError(msg) {
|
||
if (!box) return;
|
||
const v = String(msg || '').trim();
|
||
if (!v) {
|
||
box.style.display = 'none';
|
||
box.textContent = '';
|
||
return;
|
||
}
|
||
box.textContent = v;
|
||
box.style.display = 'block';
|
||
};
|
||
}
|
||
|
||
function normalizeYn(v) {
|
||
v = String(v || '').toLowerCase().trim();
|
||
if (v === 'y' || v === '1' || v === 'true' || v === 'yes' || v === 'on') return 'y';
|
||
return 'n';
|
||
}
|
||
|
||
function badgeText(yn) {
|
||
return (yn === 'y') ? '동의' : '미동의';
|
||
}
|
||
|
||
function replaceWithClone(el) {
|
||
// ✅ 기존 mypage_renew.js에서 "준비중" 리스너가 이미 붙어있으므로
|
||
// 버튼을 clone으로 교체해서 리스너를 모두 제거한다.
|
||
const clone = el.cloneNode(true);
|
||
el.parentNode.replaceChild(clone, el);
|
||
return clone;
|
||
}
|
||
|
||
function openConsentModal(currentEmail, currentSms) {
|
||
ensureStyle('mypageConsentModalStyle', `
|
||
.mypage-consentmodal{position:fixed;inset:0;z-index:220000;display:flex;align-items:center;justify-content:center}
|
||
.mypage-consentmodal__dim{position:absolute;inset:0;background:#000;opacity:.55}
|
||
.mypage-consentmodal__box{position:relative;width:min(460px,calc(100% - 28px));background:#fff;border-radius:14px;overflow:hidden;box-shadow:0 18px 60px rgba(0,0,0,.35)}
|
||
.mypage-consentmodal__hd{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:rgba(0,0,0,.04);border-bottom:1px solid rgba(0,0,0,.08)}
|
||
.mypage-consentmodal__ttl{font-weight:900;font-size:14px;color:#111}
|
||
.mypage-consentmodal__close{width:34px;height:34px;border-radius:10px;border:1px solid rgba(0,0,0,.12);background:#fff;cursor:pointer;font-size:18px;line-height:1;color:#111}
|
||
.mypage-consentmodal__bd{padding:14px}
|
||
.mypage-consentmodal__kicker{font-size:12px;font-weight:900;color:#2563eb;margin-bottom:6px}
|
||
.mypage-consentmodal__desc{font-size:12px;color:#667085;line-height:1.5;margin-bottom:12px;white-space:pre-line}
|
||
.mypage-consentmodal__grid{display:grid;grid-template-columns:1fr;gap:10px}
|
||
.mypage-consentmodal__item{border:1px solid #e5e7eb;border-radius:14px;padding:12px;background:#fff}
|
||
.mypage-consentmodal__row{display:flex;align-items:center;justify-content:space-between;gap:10px}
|
||
.mypage-consentmodal__label{font-weight:900;color:#111;font-size:13px}
|
||
.mypage-consentmodal__sub{font-size:12px;color:#667085;margin-top:6px;line-height:1.4}
|
||
.mypage-consentmodal__seg{display:inline-flex;border:1px solid rgba(0,0,0,.12);border-radius:999px;overflow:hidden;background:#fff}
|
||
.mypage-consentmodal__seg button{height:32px;min-width:64px;padding:0 12px;border:0;background:#fff;cursor:pointer;font-weight:900;font-size:12px;color:#111}
|
||
.mypage-consentmodal__seg button.is-on{background:#111;color:#fff}
|
||
.mypage-consentmodal__error{margin-top:12px;padding:10px 12px;border-radius:12px;background:rgba(220,38,38,.08);border:1px solid rgba(220,38,38,.25);color:#b91c1c;font-weight:800;font-size:12px;display:none;white-space:pre-line}
|
||
.mypage-consentmodal__ft{display:flex;gap:10px;padding:12px 14px;border-top:1px solid rgba(0,0,0,.08);background:#fff}
|
||
.mypage-consentmodal__btn{flex:1;height:42px;border-radius:12px;border:1px solid rgba(0,0,0,.12);background:#fff;font-weight:900;cursor:pointer}
|
||
.mypage-consentmodal__btn--primary{border:none;background:#111;color:#fff}
|
||
.mypage-consentmodal__btn[disabled]{opacity:.6;cursor:not-allowed}
|
||
.mypage-consentmodal__badge{font-size:11px;font-weight:900;padding:4px 10px;border-radius:999px;background:rgba(37,99,235,.10);color:#2563eb}
|
||
`);
|
||
|
||
const old = document.getElementById('mypageConsentModal');
|
||
if (old) old.remove();
|
||
|
||
const wrap = document.createElement('div');
|
||
wrap.className = 'mypage-consentmodal';
|
||
wrap.id = 'mypageConsentModal';
|
||
|
||
let emailYn = normalizeYn(currentEmail);
|
||
let smsYn = normalizeYn(currentSms);
|
||
|
||
wrap.innerHTML = `
|
||
<div class="mypage-consentmodal__dim"></div>
|
||
<div class="mypage-consentmodal__box" role="dialog" aria-modal="true" aria-labelledby="mypageConsentModalTitle">
|
||
<div class="mypage-consentmodal__hd">
|
||
<div class="mypage-consentmodal__ttl" id="mypageConsentModalTitle">마케팅 정보 수신 동의</div>
|
||
<button type="button" class="mypage-consentmodal__close" aria-label="닫기">×</button>
|
||
</div>
|
||
|
||
<div class="mypage-consentmodal__bd">
|
||
<div class="mypage-consentmodal__kicker">CONSENT SETTINGS</div>
|
||
<div class="mypage-consentmodal__desc">이벤트, 혜택, 프로모션 안내 등 마케팅 정보 수신 여부를 설정합니다.\n동의/미동의는 언제든지 변경할 수 있습니다.</div>
|
||
|
||
<div class="mypage-consentmodal__grid">
|
||
<div class="mypage-consentmodal__item">
|
||
<div class="mypage-consentmodal__row">
|
||
<div>
|
||
<div class="mypage-consentmodal__label">이메일 수신</div>
|
||
<div class="mypage-consentmodal__sub">혜택/이벤트 안내 메일을 받아볼 수 있어요.</div>
|
||
</div>
|
||
<div class="mypage-consentmodal__seg" data-kind="email">
|
||
<button type="button" data-val="y">동의</button>
|
||
<button type="button" data-val="n">미동의</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mypage-consentmodal__item">
|
||
<div class="mypage-consentmodal__row">
|
||
<div>
|
||
<div class="mypage-consentmodal__label">SMS 수신</div>
|
||
<div class="mypage-consentmodal__sub">문자메시지로 주요 프로모션을 받아볼 수 있어요.</div>
|
||
</div>
|
||
<div class="mypage-consentmodal__seg" data-kind="sms">
|
||
<button type="button" data-val="y">동의</button>
|
||
<button type="button" data-val="n">미동의</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mypage-consentmodal__error" id="consent_error"></div>
|
||
</div>
|
||
|
||
<div class="mypage-consentmodal__ft">
|
||
<button type="button" class="mypage-consentmodal__btn" data-act="cancel">취소</button>
|
||
<button type="button" class="mypage-consentmodal__btn mypage-consentmodal__btn--primary" data-act="submit">저장</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
document.body.appendChild(wrap);
|
||
|
||
const setError = makeErrorSetter(wrap, '#consent_error');
|
||
|
||
function close() { wrap.remove(); }
|
||
|
||
// 닫기: X / 취소만
|
||
wrap.querySelector('.mypage-consentmodal__close')?.addEventListener('click', close);
|
||
wrap.querySelector('[data-act="cancel"]')?.addEventListener('click', close);
|
||
|
||
function syncSeg(kind, yn) {
|
||
const seg = wrap.querySelector(`.mypage-consentmodal__seg[data-kind="${kind}"]`);
|
||
if (!seg) return;
|
||
seg.querySelectorAll('button').forEach(btn => {
|
||
const v = btn.getAttribute('data-val');
|
||
btn.classList.toggle('is-on', v === yn);
|
||
});
|
||
}
|
||
|
||
function bindSeg(kind) {
|
||
const seg = wrap.querySelector(`.mypage-consentmodal__seg[data-kind="${kind}"]`);
|
||
if (!seg) return;
|
||
seg.addEventListener('click', (e) => {
|
||
const btn = e.target?.closest?.('button[data-val]');
|
||
if (!btn) return;
|
||
const yn = btn.getAttribute('data-val') === 'y' ? 'y' : 'n';
|
||
if (kind === 'email') emailYn = yn;
|
||
if (kind === 'sms') smsYn = yn;
|
||
syncSeg(kind, yn);
|
||
});
|
||
}
|
||
|
||
syncSeg('email', emailYn);
|
||
syncSeg('sms', smsYn);
|
||
bindSeg('email');
|
||
bindSeg('sms');
|
||
|
||
async function submit() {
|
||
setError('');
|
||
|
||
if (!URLS.marketingConsentUpdate) {
|
||
setError('저장 URL이 설정되지 않았습니다.');
|
||
return;
|
||
}
|
||
|
||
const ok = await showMsg(
|
||
`수신 동의 설정을 저장하시겠습니까?\n\n• 이메일: ${badgeText(emailYn)}\n• SMS: ${badgeText(smsYn)}`,
|
||
{ type: 'confirm', title: '수신 동의 저장' }
|
||
);
|
||
if (!ok) return;
|
||
|
||
const btn = wrap.querySelector('[data-act="submit"]');
|
||
if (btn) btn.disabled = true;
|
||
|
||
try {
|
||
const res = await fetch(URLS.marketingConsentUpdate, {
|
||
method: 'POST',
|
||
credentials: 'same-origin',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-TOKEN': csrfToken(),
|
||
'Accept': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
rcv_email: emailYn,
|
||
rcv_sms: smsYn,
|
||
}),
|
||
});
|
||
|
||
const data = await res.json().catch(() => ({}));
|
||
|
||
if (res.status === 401 && data.redirect) {
|
||
await showMsg(data.message || '인증이 필요합니다.', { type: 'alert', title: '인증 필요' });
|
||
window.location.href = data.redirect;
|
||
return;
|
||
}
|
||
|
||
if (!res.ok || data.ok === false) {
|
||
const msg =
|
||
(data && data.message) ||
|
||
(data && data.errors && (data.errors.rcv_email?.[0] || data.errors.rcv_sms?.[0])) ||
|
||
'수신 동의 저장에 실패했습니다.';
|
||
setError(msg);
|
||
return;
|
||
}
|
||
|
||
await showMsg(data.message || '수신 동의 설정이 저장되었습니다.', { type: 'alert', title: '완료' });
|
||
close();
|
||
window.location.reload();
|
||
} catch (e) {
|
||
setError('네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.');
|
||
} finally {
|
||
if (btn) btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
wrap.querySelector('[data-act="submit"]')?.addEventListener('click', submit);
|
||
|
||
wrap.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
submit();
|
||
}
|
||
});
|
||
}
|
||
|
||
function boot() {
|
||
const btn0 = document.querySelector('[data-action="consent-edit"]');
|
||
if (!btn0) return;
|
||
|
||
// ✅ 기존 “준비중” 클릭 리스너 제거
|
||
const btn = replaceWithClone(btn0);
|
||
|
||
btn.addEventListener('click', () => {
|
||
const currentEmail = normalizeYn(CFG?.consent?.email ?? 'n');
|
||
const currentSms = normalizeYn(CFG?.consent?.sms ?? 'n');
|
||
openConsentModal(currentEmail, currentSms);
|
||
});
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', boot);
|
||
} else {
|
||
boot();
|
||
}
|
||
})();
|
||
|
||
|
||
|
||
|
||
// -------------------------------------------
|
||
// 5) 기타 버튼들(준비중)
|
||
// -------------------------------------------
|
||
(function others() {
|
||
|
||
$('[data-action="withdraw-member"]')?.addEventListener('click', async () => {
|
||
const ok = await showMsg(
|
||
`회원탈퇴를 진행하시겠습니까?
|
||
|
||
• 탈퇴 시 계정 복구가 어려울 수 있습니다.
|
||
• 진행 전 보유 내역/정산/환불 정책을 확인해 주세요.`,
|
||
{ type: 'confirm', title: '회원탈퇴' }
|
||
);
|
||
|
||
if (!ok) return;
|
||
|
||
await showMsg('준비중입니다.', { type: 'alert', title: '회원탈퇴' });
|
||
});
|
||
})();
|
||
|
||
})();
|