558 lines
25 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@php
$pageTitle = '나의정보';
$pageDesc = '계정 정보 관리';
$breadcrumbs = [
['label' => '홈', 'url' => url('/')],
['label' => '마이페이지', 'url' => url('/mypage/info')],
['label' => '나의정보 변경', 'url' => url()->current()],
];
$mypageActive = 'info';
@endphp
@extends('web.layouts.subpage')
@section('title', '나의정보 변경 | PIN FOR YOU')
@section('meta_description', 'PIN FOR YOU 나의정보 변경 페이지입니다.')
@section('canonical', url('/mypage/info_renew'))
@section('subcontent')
<div class="mypage-info-page">
@include('web.partials.content-head', [
'title' => '나의정보 변경',
'desc' => '계정 보안과 개인정보를 안전하게 관리하세요.'
])
{{-- 상단 상태 카드 --}}
<div class="mypage-hero mt-3">
<div class="mypage-hero__inner">
<div class="mypage-hero__left">
<div class="mypage-hero__kicker">ACCOUNT SETTINGS</div>
<div class="mypage-hero__title"> 정보 관리</div>
<div class="mypage-hero__me mt-2">
<div class="mypage-hero__me-row">
<span class="k">성명</span>
<span class="v">{{ $memberName ?: '-' }}</span>
</div>
<div class="mypage-hero__me-row">
<span class="k">이메일</span>
<span class="v">{{ $memberEmail ?: '-' }}</span>
</div>
<div class="mypage-hero__me-row">
<span class="k">휴대폰</span>
<span class="v">{{ $memberPhone ?: '-' }}</span>
</div>
<div class="mypage-hero__me-row">
<span class="k">가입일</span>
<span class="v">{{ $memberDtReg ?: '-' }}</span>
</div>
</div>
</div>
<div class="mypage-hero__right">
<div class="mypage-hero__desc">
연락처·비밀번호·보안 설정을 곳에서 관리합니다.
변경 작업은 보안을 위해 제한된 시간 동안만 가능합니다.
</div>
<div class="mypage-reauth mypage-reauth--countdown"
data-expire="{{ (int)$expireTs }}"
data-remain="{{ (int)$remainSec }}">
<div class="mypage-reauth__one">
<span class="mypage-reauth__label">인증 허용 잔여시간</span>
<span class="mypage-reauth__value">
<b id="reauthCountdown">
{{ sprintf('%02d:%02d', intdiv((int)$remainSec, 60), (int)$remainSec % 60) }}
</b>
</span>
</div>
</div>
<div class="mypage-hero__actions">
<a href="{{ route('web.mypage.info.gate_reset') }}" class="btn btn-mypage-primary">
인증 다시 하기
</a>
</div>
</div>
</div>
</div>
{{-- 설정 카드 그리드 --}}
<div class="mypage-grid mt-3">
<button type="button"
class="mypage-card mypage-card--btn"
data-action="phone-change"
data-ready-url="{{ route('web.mypage.info.pass.ready') }}">
<div class="mypage-card__icon">📱</div>
<div class="mypage-card__body">
<div class="mypage-card__title">연락처 변경</div>
<div class="mypage-card__desc">PASS 인증 휴대폰 번호를 변경합니다.</div>
<div class="mypage-card__meta">PASS 인증 필요</div>
</div>
<div class="mypage-card__arrow"></div>
</button>
<a class="mypage-card" href="javascript:void(0)" aria-label="비밀번호 변경 (준비중)" data-action="pw-change">
<div class="mypage-card__icon">🔒</div>
<div class="mypage-card__body">
<div class="mypage-card__title">비밀번호 변경</div>
<div class="mypage-card__desc">보안을 위해 주기적으로 변경을 권장해요</div>
<div class="mypage-card__meta">준비중</div>
</div>
<div class="mypage-card__arrow"></div>
</a>
<a class="mypage-card" href="javascript:void(0)" aria-label="2차 비밀번호 변경 (준비중)" data-action="pw2-change">
<div class="mypage-card__icon">🔐</div>
<div class="mypage-card__body">
<div class="mypage-card__title">2차비밀번호 변경</div>
<div class="mypage-card__desc">민감 기능 이용 추가로 확인하는 비밀번호</div>
<div class="mypage-card__meta">준비중</div>
</div>
<div class="mypage-card__arrow"></div>
</a>
<a class="mypage-card" href="javascript:void(0)" aria-label="출금계좌번호 {{ $hasWithdrawAccount ? '수정' : '등록' }}" data-action="withdraw-account">
<div class="mypage-card__icon">🏦</div>
<div class="mypage-card__body">
<div class="mypage-card__title">출금계좌번호</div>
<div class="mypage-card__desc">
{{ $hasWithdrawAccount ? '등록된 출금계좌 정보를 수정합니다.' : '출금계좌를 등록해 주세요.' }}
</div>
<div class="mypage-card__meta">
{{ $hasWithdrawAccount ? '수정' : '등록' }}
</div>
</div>
<div class="mypage-card__arrow"></div>
</a>
<button type="button" class="mypage-card mypage-card--btn" data-action="consent-edit">
<div class="mypage-card__icon">📩</div>
<div class="mypage-card__body">
<div class="mypage-card__title">수신 동의</div>
<div class="mypage-card__desc">마케팅 정보 수신 여부를 설정합니다.</div>
<div class="mypage-card__meta">
이메일: {{ ($agreeEmail === 'y' || $agreeEmail === '1') ? '동의' : '미동의' }}
· SMS: {{ ($agreeSms === 'y' || $agreeSms === '1') ? '동의' : '미동의' }}
</div>
</div>
<div class="mypage-card__arrow"></div>
</button>
<button type="button" class="mypage-card mypage-card--btn mypage-card--danger" data-action="withdraw-member">
<div class="mypage-card__icon">⚠️</div>
<div class="mypage-card__body">
<div class="mypage-card__title">회원탈퇴</div>
<div class="mypage-card__desc">계정을 삭제합니다. 진행 주의사항을 확인해 주세요.</div>
<div class="mypage-card__meta">주의 필요</div>
</div>
<div class="mypage-card__arrow"></div>
</button>
</div>
{{-- 안내/주의사항 --}}
<div class="mypage-note mt-3">
<div class="mypage-note__title">안내</div>
<ul class="mypage-note__list">
<li>개인정보 변경은 보안을 위해 <b>재인증 일정 시간</b> 동안만 가능합니다.</li>
<li>예상치 못한 오류가 발생하면, 새로고침 다시 시도해 주세요.</li>
<li>기능은 다음 단계에서 실제 처리(저장/검증/로그) 연결합니다.</li>
</ul>
</div>
</div>
<form id="mypageDanalStartForm" method="post" action="{{ route('web.mypage.info.danal.start') }}" style="display:none;">
@csrf
<input type="hidden" name="platform" id="mypagePlatform" value="web">
<input type="hidden" name="fields" id="mypagePassFields" value="">
</form>
@push('styles')
<style>
.mypage-reauth__one{
display:flex;
align-items:center;
justify-content:space-between;
gap:10px;
}
#reauthCountdown{
font-size:14px;
letter-spacing:0.5px;
}
/* 더 강한 특이도(덮임 방지) + 흰 배경에서도 확실히 보이게 */
.mypage-info-page .mypage-hero .mypage-hero__me{
margin-top:12px;
padding:14px;
border-radius:14px;
background:#f7f8fb; /* ✅ 흰배경에서도 티 나게 */
border:1px solid #e5e7eb;
box-shadow: 0 10px 24px rgba(16,24,40,.08);
}
.mypage-info-page .mypage-hero .mypage-hero__me-row{
display:flex;
align-items:center;
justify-content:space-between;
gap:12px;
padding:10px 12px;
border-radius:12px;
background:#ffffff;
border:1px solid rgba(0,0,0,.04);
}
.mypage-info-page .mypage-hero .mypage-hero__me-row + .mypage-hero__me-row{
margin-top:8px;
}
.mypage-info-page .mypage-hero .mypage-hero__me-row:hover{
transform: translateY(-1px);
box-shadow: 0 6px 16px rgba(16,24,40,.06);
}
.mypage-info-page .mypage-hero .mypage-hero__me-row .k{
display:inline-flex;
align-items:center;
gap:8px;
font-size:12px;
font-weight:800;
color:#667085;
white-space:nowrap;
}
.mypage-info-page .mypage-hero .mypage-hero__me-row .k::before{
content:'';
width:8px;
height:8px;
border-radius:999px;
background:#2563eb; /* 포인트 컬러 */
box-shadow: 0 0 0 4px rgba(37,99,235,.12);
}
.mypage-info-page .mypage-hero .mypage-hero__me-row .v{
font-size:14px;
font-weight:900;
color:#101828;
letter-spacing:.2px;
text-align:right;
word-break:break-all;
}
/* 모바일 */
@media (max-width: 480px){
.mypage-info-page .mypage-hero .mypage-hero__me{
padding:12px;
border-radius:12px;
}
.mypage-info-page .mypage-hero .mypage-hero__me-row{
padding:10px 10px;
}
.mypage-info-page .mypage-hero .mypage-hero__me-row .v{
font-size:13px;
}
}
</style>
@endpush
@push('scripts')
<script>
/**
* 1) ✅ 재인증 타이머 (버튼 유무와 무관하게 항상 실행)
* - remainSec 기반 카운트다운
* - 0 되면 alert → info 페이지로 이동
*/
(function () {
const box = document.querySelector('.mypage-reauth--countdown');
const out = document.getElementById('reauthCountdown');
if (!box || !out) return;
const redirectUrl = "{{ route('web.mypage.info.gate_reset') }}";
// ✅ 서버가 내려준 세션 until(문자열) 우선 사용
const untilStr = (box.getAttribute('data-until') || '').trim();
// fallback: 렌더링 시점 remain
const remainFallback = parseInt(box.getAttribute('data-remain') || '0', 10);
// until 파싱 (서버가 'YYYY-MM-DD HH:MM:SS'로 주면 JS Date가 못 읽는 경우가 있음)
// 그래서 'YYYY-MM-DDTHH:MM:SS'로 변환해서 파싱 시도
function parseUntilMs(s){
if (!s) return null;
// 2026-02-01 16:33:50 -> 2026-02-01T16:33:50
const isoLike = s.includes('T') ? s : s.replace(' ', 'T');
const ms = Date.parse(isoLike);
return Number.isFinite(ms) ? ms : null;
}
const untilMs = parseUntilMs(untilStr);
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');
}
let done = false;
let timer = null;
function expireUI(){
const chip = document.querySelector('.mypage-chip');
if (chip) {
chip.classList.remove('is-ok');
chip.classList.add('is-warn');
chip.textContent = '재인증 필요';
}
}
async function onExpiredOnce() {
await showMsg("인증 허용 시간이 만료되었습니다.\n\n보안을 위해 다시 재인증이 필요합니다.", { type: 'alert', title: '인증완료' });
window.location.href = "{{ route('web.mypage.info.gate_reset') }}";
}
function getRemainSec(){
// ✅ untilMs가 유효하면 "세션 until - 현재시간"으로 매번 계산
if (untilMs !== null) {
const diffMs = untilMs - Date.now();
return Math.max(0, Math.floor(diffMs / 1000));
}
// fallback: remainFallback에서 감소시키는 방식(최후의 수단)
return Math.max(0, Number.isFinite(remainFallback) ? remainFallback : 0);
}
// fallback용 remain 변수 (untilMs가 없을 때만 사용)
let remain = Math.max(0, Number.isFinite(remainFallback) ? remainFallback : 0);
function tick(){
const sec = (untilMs !== null) ? getRemainSec() : remain;
out.textContent = fmt(sec);
if (sec <= 0) {
if (timer) clearInterval(timer);
onExpiredOnce();
return;
}
// untilMs가 없을 때만 1초씩 감소
if (untilMs === null) {
remain -= 1;
}
}
tick();
timer = setInterval(tick, 1000);
})();
/**
* 2) ✅ 연락처 변경 버튼 → passReady → danal iframe 모달
* (기존 로직 유지, 타이머와 분리)
*/
(function(){
const btn = document.querySelector('[data-action="phone-change"]');
const startForm = document.getElementById('mypageDanalStartForm');
// 버튼이 없는 페이지에서도 타이머는 돌아야 하므로, 여기서만 return
if (!btn || !startForm) return;
function isMobileUA(){
const ua = navigator.userAgent || '';
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua);
}
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();
}
function askCancelAndGo() {
//시스템 confirm
const ok = window.confirm("인증을 중단하시겠습니까?\n\n닫으면 현재 변경 진행이 취소됩니다.");
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);
const escHandler = (e) => { if (e.key === 'Escape') askCancelAndGo(); };
window.addEventListener('keydown', escHandler);
window.closeIframe = function () {
window.removeEventListener('keydown', escHandler);
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 = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
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 function(){
const readyUrl = btn.getAttribute('data-ready-url');
if (!readyUrl) {
await showMsg('준비 URL이 없습니다(data-ready-url).', { type: 'alert', title: '오류' });
return;
}
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': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
'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 () {
const $ = (sel) => document.querySelector(sel);
// 비밀번호 변경
$('[data-action="pw-change"]')?.addEventListener('click', async () => {
await showMsg('준비중입니다.', { type: 'alert', title: '비밀번호 변경' });
});
// 2차 비밀번호 변경
$('[data-action="pw2-change"]')?.addEventListener('click', async () => {
await showMsg('준비중입니다.', { type: 'alert', title: '2차비밀번호 변경' });
});
// 출금계좌 등록/수정
$('[data-action="withdraw-account"]')?.addEventListener('click', async () => {
await showMsg('준비중입니다.', { type: 'alert', title: '출금계좌번호' });
});
// 수신동의 수정
$('[data-action="consent-edit"]')?.addEventListener('click', async () => {
await showMsg('준비중입니다.', { type: 'alert', title: '수신 동의' });
});
// 회원탈퇴
$('[data-action="withdraw-member"]')?.addEventListener('click', async () => {
const ok = await showMsg(
`회원탈퇴를 진행하시겠습니까?
• 탈퇴 시 계정 복구가 어려울 수 있습니다.
• 진행 전 보유 내역/정산/환불 정책을 확인해 주세요.`,
{ type: 'confirm', title: '회원탈퇴' }
);
if (!ok) return;
await showMsg('준비중입니다.', { type: 'alert', title: '회원탈퇴' });
});
})();
</script>
@endpush
@endsection