diff --git a/app/Repositories/Member/MemberAuthRepository.php b/app/Repositories/Member/MemberAuthRepository.php index 21965f6..296ccac 100644 --- a/app/Repositories/Member/MemberAuthRepository.php +++ b/app/Repositories/Member/MemberAuthRepository.php @@ -667,6 +667,33 @@ class MemberAuthRepository } } + //회원 탈퇴 1차 비밀번호 검증 + public function verifyLegacyPassword(int $memNo, string $pwPlain): bool + { + $pwPlain = (string) $pwPlain; + + if ($memNo <= 0 || $pwPlain === '') { + return false; + } + + // ✅ 1차 비번은 mem_st_ring.str_0 + $stored = (string) (DB::table('mem_st_ring') + ->where('mem_no', $memNo) + ->value('str_0') ?? ''); + + if ($stored === '') { + return false; + } + + // ✅ CI 방식(PASS_SET=0) - 기존 attemptLegacyLogin과 동일 + $try = (string) CiPassword::make($pwPlain, 0); + if ($try === '') { + return false; + } + + return hash_equals($stored, $try); + } + // 2차 비밀번호 검증 public function verifyPin2(int $memNo, string $pin2Plain): bool { diff --git a/app/Services/MemInfoService.php b/app/Services/MemInfoService.php index 0592f63..f737380 100644 --- a/app/Services/MemInfoService.php +++ b/app/Services/MemInfoService.php @@ -572,7 +572,7 @@ class MemInfoService // ✅ 최근 7일 이내 구매내역(stat_pay p/t) 있으면 불가 $from = Carbon::today()->subDays(7)->startOfDay(); - $cnt = (int) DB::table('giftcard_order') // ⚠️ 테이블명이 다르면 여기만 바꾸면 됨 + $cnt = (int) DB::table('pin_order') ->where('mem_no', $memNo) ->whereIn('stat_pay', ['p','t']) ->where('dt_stat_pay', '>=', $from->toDateTimeString()) @@ -607,13 +607,12 @@ class MemInfoService 'name_first' => '', 'name_mid' => '', 'name_last' => '', - 'birth' => '', + 'birth' => '1900-01-01', 'gender' => '', 'native' => '', 'cell_corp' => '', 'cell_phone' => '', 'ci' => '', - 'bank_code' => '', 'bank_name' => '', 'bank_act_num' => '', @@ -622,7 +621,6 @@ class MemInfoService 'dt_req_out' => $dtReqOut, 'dt_out' => $now->toDateTimeString(), 'stat_3' => '4', - 'dt_mod' => $now->toDateTimeString(), ]); @@ -630,8 +628,6 @@ class MemInfoService DB::table('mem_account') ->where('mem_no', $memNo) ->update([ - 'act_type' => '', - 'act_state' => '', 'bank_code' => '', 'bank_name' => '', 'bank_act_name' => '', @@ -642,7 +638,6 @@ class MemInfoService DB::table('mem_address') ->where('mem_no', $memNo) ->update([ - 'gubun' => '', 'shipping' => '', 'zipNo' => '', 'roadAddrPart1' => '', diff --git a/public/assets/css/mypage_renew.css b/public/assets/css/mypage_renew.css index 669e5fe..ab4a059 100644 --- a/public/assets/css/mypage_renew.css +++ b/public/assets/css/mypage_renew.css @@ -161,3 +161,184 @@ font-size:13px; } } + +/* ========================================= + Withdraw Dialog (mypage style) +========================================= */ + +/* dim */ +.ui-dialog.ui-dialog--mypage .ui-dialog__backdrop{ + background: rgba(16,24,40,.45); + backdrop-filter: blur(2px); +} + +/* panel */ +.ui-dialog__panel--mypage{ + width: min(640px, calc(100vw - 32px)); + border-radius: 18px; + background: #ffffff; + border: 1px solid #e5e7eb; + box-shadow: 0 18px 50px rgba(16,24,40,.18); + overflow: hidden; +} + +/* head */ +.ui-dialog__head--mypage{ + display:flex; + align-items:center; + justify-content:space-between; + padding: 16px 18px; + background: #ffffff; + border-bottom: 1px solid rgba(0,0,0,.06); +} + +.ui-dialog__title--mypage{ + font-size: 15px; + font-weight: 900; + color: #101828; + letter-spacing: .2px; +} + +.ui-dialog__close--mypage{ + width: 34px; + height: 34px; + border-radius: 12px; + border: 1px solid rgba(0,0,0,.06); + background: #f7f8fb; + color: #101828; + font-weight: 900; + cursor: pointer; +} + +/* body */ +.ui-dialog__body--mypage{ + padding: 16px 18px 18px; + background: #ffffff; +} + +/* card blocks */ +.wd-card{ + border-radius: 16px; + border: 1px solid rgba(0,0,0,.06); + background: #f7f8fb; + padding: 14px; +} + +.wd-card + .wd-card{ margin-top: 12px; } + +.wd-card--form{ + background: #ffffff; +} + +/* guide list */ +.wd-guide{ + margin: 0; + padding-left: 18px; + color: #475467; + font-size: 13px; + line-height: 1.65; +} + +.wd-guide li + li{ margin-top: 6px; } + +/* agree */ +.wd-agree{ + display:flex; + align-items:center; + gap:10px; + margin-top: 12px; + font-weight: 800; + color: #101828; +} +.wd-agree input{ + width: 16px; + height: 16px; + accent-color: #2563eb; +} + +/* fields */ +.wd-field + .wd-field{ margin-top: 12px; } + +.wd-label{ + display:block; + font-size: 12px; + font-weight: 800; + color: #667085; + margin-bottom: 6px; +} +.wd-label small{ + font-weight: 800; + color: #98a2b3; +} + +.wd-input{ + width: 100%; + height: 44px; + border-radius: 14px; + border: 1px solid rgba(0,0,0,.10); + background: #ffffff; + padding: 0 12px; + font-size: 14px; + font-weight: 800; + color: #101828; + outline: none; +} + +.wd-input:focus{ + border-color: rgba(37,99,235,.55); + box-shadow: 0 0 0 4px rgba(37,99,235,.12); +} + +/* error msg */ +.wd-msg{ + margin-top: 12px; + padding: 10px 12px; + border-radius: 14px; + background: #fff1f2; + border: 1px solid rgba(225,29,72,.18); + color: #9f1239; + font-weight: 900; + font-size: 13px; +} + +/* footer buttons */ +.ui-dialog__foot--mypage{ + display:flex; + gap:10px; + padding: 14px 18px 18px; + background: #ffffff; + border-top: 1px solid rgba(0,0,0,.06); +} + +.wd-btn{ + flex: 1; + height: 44px; + border-radius: 14px; + font-weight: 900; + font-size: 14px; + cursor: pointer; +} + +.wd-btn--ghost{ + background: #ffffff; + border: 1px solid rgba(0,0,0,.10); + color: #101828; +} + +.wd-btn--danger{ + background: #f97316; /* 사이트 메인 오렌지 쓰면 여기 바꿔도 됨 */ + border: 1px solid rgba(0,0,0,.06); + color: #ffffff; +} + +.wd-btn:disabled{ + opacity: .6; + cursor: not-allowed; +} + +/* mobile */ +@media (max-width: 480px){ + .ui-dialog__panel--mypage{ border-radius: 16px; } + .ui-dialog__body--mypage{ padding: 14px; } + .ui-dialog__foot--mypage{ padding: 12px 14px 14px; } +} diff --git a/public/assets/js/mypage_renew.js b/public/assets/js/mypage_renew.js index fd6e773..fb9e23f 100644 --- a/public/assets/js/mypage_renew.js +++ b/public/assets/js/mypage_renew.js @@ -1202,26 +1202,104 @@ })(); + (function () { + const dlg = document.getElementById('withdrawDialog'); + if (!dlg) return; + + const openBtn = document.getElementById('btnOpenWithdraw'); // 아래에서 버튼 추가할 거임 + const closeEls = dlg.querySelectorAll('[data-close="1"]'); + const submitBtn = document.getElementById('withdrawSubmitBtn'); + + const agreeEl = document.getElementById('withdrawAgree'); + const pwEl = document.getElementById('withdrawPw'); + const pin2El = document.getElementById('withdrawPin2'); + const msgEl = document.getElementById('withdrawMsg'); + + function openDialog() { + dlg.classList.add('is-open'); + dlg.setAttribute('aria-hidden', 'false'); + msgEl.style.display = 'none'; + msgEl.textContent = ''; + // reset + agreeEl.checked = false; + pwEl.value = ''; + pin2El.value = ''; + setTimeout(() => pwEl.focus(), 50); + } + + function closeDialog() { + dlg.classList.remove('is-open'); + dlg.setAttribute('aria-hidden', 'true'); + } + + function showError(text) { + msgEl.style.display = 'block'; + msgEl.textContent = text; + } + + closeEls.forEach(el => el.addEventListener('click', closeDialog)); + if (openBtn) openBtn.addEventListener('click', openDialog); + + // 숫자만 + pin2El.addEventListener('input', function () { + this.value = (this.value || '').replace(/[^\d]/g, '').slice(0, 4); + }); + + submitBtn.addEventListener('click', async function () { + if (!agreeEl.checked) return showError('안내사항 동의가 필요합니다.'); + const password = (pwEl.value || '').trim(); + const pin2 = (pin2El.value || '').trim(); + if (!password) return showError('비밀번호를 입력해 주세요.'); + if (!/^\d{4}$/.test(pin2)) return showError('2차 비밀번호는 숫자 4자리여야 합니다.'); + + submitBtn.disabled = true; + + try { + const res = await fetch(dlg.dataset.action, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + 'X-CSRF-TOKEN': dlg.dataset.csrf, + }, + body: JSON.stringify({ + agree: '1', + password, + pin2 + }) + }); + + const json = await res.json().catch(() => ({})); + + if (!res.ok || !json.ok) { + showError(json.message || '회원탈퇴에 실패했습니다.'); + submitBtn.disabled = false; + return; + } + + // 성공 + if (typeof showMsg === 'function') { + await showMsg(json.message || '회원탈퇴가 완료되었습니다.', { type: 'alert', title: '완료' }); + } else { + alert(json.message || '회원탈퇴가 완료되었습니다.'); + } + + window.location.href = json.redirect || '/'; + + } catch (e) { + showError('처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'); + submitBtn.disabled = false; + } + }); + })(); + + + // ------------------------------------------- // 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: '회원탈퇴' }); - }); - })(); })(); diff --git a/resources/views/web/mypage/info/_withdraw_dialog.blade.php b/resources/views/web/mypage/info/_withdraw_dialog.blade.php new file mode 100644 index 0000000..4d7d1e0 --- /dev/null +++ b/resources/views/web/mypage/info/_withdraw_dialog.blade.php @@ -0,0 +1,48 @@ +
diff --git a/resources/views/web/mypage/info/renew.blade.php b/resources/views/web/mypage/info/renew.blade.php index d0053aa..bf2d83a 100644 --- a/resources/views/web/mypage/info/renew.blade.php +++ b/resources/views/web/mypage/info/renew.blade.php @@ -167,7 +167,7 @@