/* 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 = `
`; 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 = `