Files
moneyguide/imweb/imweb-free-apply.html
ttipo 38e945767b
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
feat: 신청 때 최종 유입 저장과 신청 유입 집계 추가
가입 경로와 신청(수강) 유입을 구분해 보기 위해서다. 기존 신청 유입은 신청 시점
브라우저의 '최초' 유입(2일 보관)이라, 그 사이 다른 경로로 다시 와서 신청하면
신청을 일으킨 경로가 가려졌다.

- member_applications 에 최종 유입(inflow_last_*) 저장 — 없으면 최초 유입으로 채움 (기존 행도 동일)
- 신청 완료 위젯이 MG_UTM.peek().last 를 lastInflow 로 함께 보냄
- GET /api/admin/applications/inflow-stats: 신청 때 최종·최초 / 가입 경로(가입 때 기록만) 기준 집계
- 신청 목록에 inflowBasis·inflowField·inflowValue 필터
- 관리자 화면 신청 내역: '신청 유입 집계' 카드(기준·항목 전환, 막대 클릭으로 목록 필터),
  신청 때 유입 칸에 최종 유입 표시 + 최초가 다르면 함께 표시

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:22:02 +09:00

678 lines
29 KiB
HTML

<!-- ===== 아임웹 HTML 위젯 삽입용 코드 (본문 스니펫) - 무료 신청 완료 ===== -->
<!-- ※ 신청 위젯(course-enroll.html)과 같은 페이지에 배치하세요. 신청 완료 시 자동으로 표시됩니다. -->
<div class="moneyguider-complete">
<div class="moneyguider-complete__card">
<div class="moneyguider-complete__icon" aria-hidden="true">
<svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="#1a1200" stroke-width="3"
stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
<path d="M20 6L9 17l-5-5" />
</svg>
</div>
<h2 class="moneyguider-complete__title">신청이 완료되었습니다.</h2>
<hr class="moneyguider-complete__divider" />
<div class="moneyguider-complete__info">
<div class="moneyguider-complete__row">
<span class="moneyguider-complete__label">신청강의</span>
<span class="moneyguider-complete__value">[무료한정 😳] 하루 2시간 월1000 버는 AI 효자 상품 찾기</span>
</div>
</div>
<p class="moneyguider-complete__guide">
무료 라이브 단톡방에 입장하셔서<br />
<strong>전자책, 무료강의 등 다양한 혜택</strong>을 받아가세요!
</p>
<a class="moneyguider-complete__kakao" href="https://open.kakao.com/o/gWNuX9Eh" target="_blank"
rel="noopener noreferrer">
<svg width="18" height="18" viewBox="0 0 24 24" fill="#1a1200" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 3C6.5 3 2 6.6 2 11c0 2.8 1.9 5.3 4.7 6.7-.2.7-.7 2.6-.8 3-.1.5.2.5.4.4.2-.1 2.6-1.8 3.7-2.5.7.1 1.4.2 2 .2 5.5 0 10-3.6 10-8S17.5 3 12 3z" />
</svg>
오픈카톡 입장하기
</a>
<a class="moneyguider-complete__secondary" href="/home">메인 페이지로 이동</a>
</div>
</div>
<style>
/* moneyguider 신청 완료 위젯 전용 스코프 스타일 (사이트 전역에 영향 없음) */
.moneyguider-complete {
box-sizing: border-box;
width: 100%;
font-family: "Pretendard", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
-webkit-font-smoothing: antialiased;
/* 기본 숨김: 신청 완료 시 JS가 .is-shown 클래스를 추가해 표시 */
display: none;
}
.moneyguider-complete.is-shown {
display: block;
}
.moneyguider-complete * {
box-sizing: border-box;
}
.moneyguider-complete__card {
width: 100%;
max-width: 560px;
margin: 0 auto;
padding: 44px 36px 36px;
background-color: #141414;
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 24px;
text-align: center;
}
/* 체크 아이콘 */
.moneyguider-complete__icon {
display: flex;
align-items: center;
justify-content: center;
width: 72px;
height: 72px;
margin: 0 auto 22px;
background-color: #f5b727;
border-radius: 50%;
}
.moneyguider-complete .moneyguider-complete__title {
margin: 0;
font-size: 26px;
font-weight: 800;
line-height: 1.35;
color: #ffffff;
letter-spacing: -0.02em;
text-indent: 0;
font-style: normal;
}
.moneyguider-complete__divider {
height: 1px;
margin: 26px 0;
border: 0;
background-color: rgba(255, 255, 255, 0.08);
}
/* 신청 정보 박스 */
.moneyguider-complete__info {
padding: 22px 24px;
background-color: #1a1a1a;
border-radius: 14px;
text-align: left;
}
.moneyguider-complete__row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.moneyguider-complete__label {
flex: 0 0 auto;
font-size: 15px;
font-weight: 700;
color: #cfcfcf;
}
.moneyguider-complete__value {
font-size: 15px;
line-height: 1.5;
color: #ffffff;
text-align: right;
}
/* 안내 문구 */
.moneyguider-complete__guide {
margin: 26px 0 22px;
font-size: 15px;
line-height: 1.6;
color: #9a9a9a;
}
.moneyguider-complete__guide strong {
font-weight: 700;
color: #f5b727;
}
/* 버튼 */
.moneyguider-complete__kakao {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 17px;
margin-bottom: 12px;
background-color: #fee500;
border-radius: 12px;
font-size: 16px;
font-weight: 700;
color: #1a1200 !important;
text-decoration: none;
transition: filter 0.2s ease, transform 0.2s ease;
}
.moneyguider-complete__kakao:hover {
filter: brightness(0.96);
transform: translateY(-2px);
}
.moneyguider-complete__secondary {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 17px;
background-color: transparent;
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 12px;
font-size: 16px;
font-weight: 700;
color: #ffffff !important;
text-decoration: none;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
.moneyguider-complete__secondary:hover {
background-color: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.3);
}
/* 반응형 */
@media (max-width: 520px) {
.moneyguider-complete__card {
padding: 36px 22px 26px;
border-radius: 20px;
}
.moneyguider-complete .moneyguider-complete__title {
font-size: 22px;
}
.moneyguider-complete__row {
flex-direction: column;
gap: 4px;
}
.moneyguider-complete__value {
text-align: left;
}
}
</style>
<script>
(function () {
'use strict';
/* ========================================================
▼▼▼ 여기만 수정하시면 됩니다 ▼▼▼
======================================================== */
/* 1) 비로그인 사용자를 이 화면에서 튕겨낼지.
주소창에 /apply 를 직접 쳐서 들어오는 것을 막습니다. */
var REQUIRE_LOGIN = true;
/* 2) 튕겨낼 때 어디로 보낼지
'join' 아임웹 회원가입 페이지로 보냅니다. 가입이 끝나면
지금 주소(/apply?...)로 그대로 돌아와 완료 화면이
보입니다. 신청 흐름이 끊기지 않습니다.
주소 그 주소로 이동시킵니다.
예) '/free-shop/?idx=52' '/home' */
var REDIRECT_TO = 'join';
/* 3) 회원가입 페이지 주소 ('가입 방식 선택' 화면).
back_url 파라미터는 자동으로 붙습니다.
detail-buy-btn.html 의 JOIN_PAGE 와 같은 값이어야 합니다.
※ SITE_MEMBER.openJoinTypeChoice() 를 부르면 같은 화면이
현재 페이지 위에 '모달' 로 뜹니다. 이 페이지는 내용을
감춰 둔 상태라 모달 뒤가 텅 비어 보이고, 사용자가 모달을
닫으면 빈 화면에 갇힙니다. 전체 페이지로 보냅니다. */
var JOIN_PAGE = '/site_join_type_choice';
/* 4) 광고 픽셀 이벤트
------------------------------------------------------
※ '오픈카톡방에 실제로 입장했는지' 는 우리 쪽에서 알 수
없습니다. open.kakao.com 은 카카오 도메인이고, 입장
결과를 사이트로 알려주는 콜백이 없습니다.
그래서 '입장하기 버튼 클릭' 을 전환으로 잡습니다.
실제 입장률만큼 과대 집계되는 점을 감안하세요. */
var PIXEL = {
/* 켜고 끄기 */
ON: true,
/* (1) 오픈카톡 버튼 클릭 → 이것을 광고 전환으로 잡으시면 됩니다.
메타 광고 관리자에는 '구독' 으로 표시됩니다.
Subscribe 를 고른 이유
- 캠페인 목표가 '판매' 면 전환 이벤트 목록에 커머스
계열만 나옵니다. Contact / Lead 는 '잠재 고객' 목표
전용이라 목록에 아예 안 뜹니다.
- Subscribe 는 그 목록 안에 있으면서 아직 아무도
쓰지 않는 이벤트입니다. (결제 시작·콘텐츠 조회·
장바구니·구매는 아임웹 쇼핑 연동이 이미 사용 중,
등록 완료는 아래 가입 전환이 사용 중)
- 의미도 '채널 입장 = 구독' 으로 가장 가깝습니다.
※ 주의 : 상품 타입이 subscribe 로 잡혀 있어
(data-bs-prod-type="subscribe"), 정규강의 결제가
완료되면 아임웹이 Purchase 대신 Subscribe 를 쏠
가능성이 있습니다. 그때는 이벤트 관리자에서 '구독'
이벤트의 소스 URL 을 확인해 /apply 외의 주소가
섞이는지 보세요. 섞인다면 URL 조건을 건 맞춤 전환으로
분리하시면 됩니다.
맞춤 전환으로 따로 관리하시려면 KAKAO_CUSTOM 을 true 로
두세요. 그러면 trackCustom 으로 보냅니다. */
KAKAO_EVENT: 'Subscribe',
KAKAO_CUSTOM: false,
/* (2) 회원가입 완료 → CompleteRegistration ('등록 완료')
지금은 꺼져 있습니다. 위 (1) 구독 추적만 동작합니다.
true 로 바꾸면 가입 전환도 함께 집계됩니다. 그때 필요한
표식은 detail-buy-btn.html 이 이미 남기고 있으므로
여기 값만 바꾸면 되고, 다른 파일은 손대지 않아도 됩니다.
켤 경우의 동작 : 가입 페이지를 거쳐 돌아온 사람만 셉니다.
이미 회원인 사람이 완료 화면에 다시 들른 경우는
세지 않습니다. */
REGISTRATION: false,
/* (3) 구글 광고 전환 — 전환 액션 '회원가입 (광섭)'
구글은 메타처럼 이벤트 이름으로 구분하지 않고,
전환 액션마다 발급되는 send_to 값으로 구분합니다.
구글 애즈 > 목표 > 해당 전환 액션 > 태그 설정 에서
'AW-계정ID/라벨' 형태로 확인할 수 있습니다.
비워두면 구글 쪽으로는 아무것도 보내지 않습니다. */
GOOGLE_KAKAO: 'AW-17296567556/95baCOj7ze0aEITa0rdA',
/* 가입 완료 시점에 보낼 구글 전환. 지금은 비워 두었습니다.
(아래 REGISTRATION 이 false 라 어차피 동작하지 않습니다)
가입 시점으로 옮기시려면 위 값을 여기로 옮기고
REGISTRATION 을 true 로 바꾸세요. */
GOOGLE_SIGNUP: '',
/* (4) GA4 이벤트도 함께 보낼지.
※ 현재 이 사이트에는 GA4(G-) 속성이 없고 구글 광고
태그(AW-)만 설치돼 있습니다. 그래서 켜 두어도 지금은
실질적인 효과가 없습니다. 나중에 GA4 를 붙이면
그때부터 동작합니다. */
GA4: true
};
/* detail-buy-btn.html 이 남기는 표식의 이름. 양쪽이 같아야 합니다. */
var JOIN_FLAG = 'mg-join-started';
/* 6) 상품별 오픈카톡 링크
키 : 상품번호 — 주소의 ?idx= 값입니다.
detail-buy-btn.html 이 신청 버튼에서 함께 넘겨줍니다.
값 : 그 상품 전용 오픈채팅방 주소
여기에 없는 상품은 아래 KAKAO_DEFAULT 로 갑니다. */
var KAKAO_LINKS = {
'52': 'https://open.kakao.com/o/gWNuX9Eh'
// '10': 'https://open.kakao.com/o/XXXXXXXX',
};
/* 등록되지 않은 상품이거나 상품번호 없이 들어온 경우에 쓸 링크.
비워두면('') 오픈카톡 버튼 자체를 숨깁니다.
엉뚱한 방으로 보내느니 안 보여주는 편이 낫다면 비우세요. */
var KAKAO_DEFAULT = '';
/* 5) 로그인 여부를 판별하는 데 기다릴 최대 시간.
아임웹 전역(MEMBER_UID)이 늦게 실행되는 경우 대비입니다.
이 시간이 지나도 판별이 안 되면 '통과' 시킵니다. */
var WAIT_MS = 2000;
var POLL_MS = 100;
/* 7) 신청 기록을 보낼 백엔드 주소
신청 완료 화면이 뜰 때 회원·상품번호·유입정보·업체 링크 클릭을
보내 관리자 화면 '신청 내역'과 '업체 링크' 실적에 남깁니다.
비워두면('') 아무것도 보내지 않습니다. */
var API_BASE = 'https://moneyguide.wandergo.net/api';
/* ========================================================
▲▲▲ 여기까지 ▲▲▲ 아래는 수정할 필요 없습니다.
======================================================== */
var el = document.querySelector('.moneyguider-complete');
if (!el) return;
var params = new URLSearchParams(window.location.search);
var name = params.get('name');
/* 상품번호 — detail-buy-btn.html 이 붙여 보냅니다.
오픈카톡 링크를 고르고, 픽셀에도 함께 실어 보냅니다. */
var idx = params.get('idx') || '';
var isApplyPage = window.location.pathname.indexOf('apply') !== -1;
// /apply 페이지도 아니고 name 파라미터도 없으면 아무것도 하지 않습니다
if (!isApplyPage && !name) return;
/* ── 픽셀 전송 ────────────────────────────────────────
픽셀이 아직 안 떴거나 광고차단기에 막혀도 페이지가 죽지
않도록 전부 방어합니다. 완료 화면 표시가 픽셀보다 우선입니다. */
function meta(name, custom, params) {
if (!PIXEL.ON || typeof window.fbq !== 'function') return;
try { window.fbq(custom ? 'trackCustom' : 'track', name, params || {}); }
catch (e) { }
}
function ga(name, params) {
if (!PIXEL.ON || !PIXEL.GA4 || typeof window.gtag !== 'function') return;
try { window.gtag('event', name, params || {}); } catch (e) { }
}
/* 구글 광고 전환.
구글이 안내하는 스니펫은 gtag_report_conversion(url) 형태로,
전환을 보낸 뒤 window.location 으로 이동시키는 구조입니다.
그건 '같은 탭에서 이동하는 링크' 를 위한 것입니다.
우리 카톡 버튼은 target="_blank" 라 현재 페이지가 그대로
남으므로 이동을 가로챌 이유가 없습니다. 그 방식을 쓰면
새 탭이 열리면서 현재 탭까지 카톡으로 넘어가 버립니다.
→ 전환만 보내고 링크는 건드리지 않습니다. */
function gads(sendTo) {
if (!PIXEL.ON || !sendTo || typeof window.gtag !== 'function') return;
try { window.gtag('event', 'conversion', { 'send_to': sendTo }); }
catch (e) { }
}
function ssGet(key) {
try { return window.sessionStorage.getItem(key); }
catch (e) { return null; }
}
function ssDrop(key) {
try { window.sessionStorage.removeItem(key); } catch (e) { }
}
/* 픽셀 스크립트는 비동기로 뜹니다. 페이지 로드 직후에는 아직
window.fbq 가 없을 수 있어 잠깐 기다립니다.
이게 필요한 이유 : 가입 전환은 '표식을 지우고 → 보낸다' 인데,
픽셀이 안 뜬 상태에서 표식부터 지워버리면 그 가입은 영영
집계되지 않습니다. 표식은 실제로 보낸 뒤에만 지웁니다. */
var PIXEL_WAIT_MS = 4000;
function whenPixelReady(cb) {
var waited = 0;
(function poll() {
var ready = (typeof window.fbq === 'function');
if (ready || waited >= PIXEL_WAIT_MS) { cb(ready); return; }
waited += 100;
window.setTimeout(poll, 100);
})();
}
/* ── 신청 기록 전송 ──────────────────────────────────────
실패해도 완료 화면·픽셀에는 영향을 주지 않습니다 (전부 조용히 무시).
같은 회원·상품은 서버가 한 건만 남기므로 새로고침해도 안전합니다. */
var PRODUCT_IDX_PATTERN = /^\d{1,20}$/;
var PRODUCT_NAME_MAX = 200;
var INFLOW_MAX = 1000;
var applicationRequest = null; /* 신청 기록 ID 를 돌려주는 Promise */
/* 아임웹 전역(MEMBER_UID)이 늦게 생기는 경우를 잠깐 기다립니다 */
function whenMemberUid(cb) {
var waited = 0;
(function poll() {
var uid = window.MEMBER_UID;
if (uid) { cb(String(uid)); return; }
if (typeof uid === 'string' || waited >= WAIT_MS) return;
waited += POLL_MS;
window.setTimeout(poll, POLL_MS);
})();
}
/* imweb-header-top.html(UTM 스크립트)이 보관한 유입정보 — 없으면 빈 값.
MG_UTM.peek() 는 { first, last, value, … } 객체를 돌려주고,
서버가 받는 파이프 형식(src=..|med=..|…)은 그중 value 다. */
function readInflow() {
try {
var peeked = window.MG_UTM && typeof window.MG_UTM.peek === 'function'
? window.MG_UTM.peek() : null;
var value = peeked && typeof peeked === 'object' ? peeked.value : peeked;
return typeof value === 'string' ? value.slice(0, INFLOW_MAX) : '';
} catch (e) { return ''; }
}
/* 신청 직전의 최종 유입 — MG_UTM.peek().last 객체를 위와 같은 파이프 형식으로 바꾼다.
최초 유입(위 value)은 2일간 처음 들어온 경로라, 그 사이 다른 경로로 다시 와서
신청하면 신청을 일으킨 경로는 last 에만 있다. 없으면 빈 값 (서버가 최초 유입으로 채운다) */
var LAST_KEYS = ['src', 'med', 'cmp', 'trm', 'cnt', 'cid', 'ref', 'lp', 'pid', 'ts'];
function readLastInflow() {
try {
var peeked = window.MG_UTM && typeof window.MG_UTM.peek === 'function'
? window.MG_UTM.peek() : null;
var last = peeked && typeof peeked === 'object' ? peeked.last : null;
if (!last || typeof last !== 'object' || !last.src) return '';
var parts = [];
for (var i = 0; i < LAST_KEYS.length; i++) {
var v = last[LAST_KEYS[i]];
/* UTM 스크립트가 이미 | = 를 지운 값이지만 한 번 더 막는다 */
if (v) parts.push(LAST_KEYS[i] + '=' + String(v).replace(/[|=]/g, ' '));
}
return parts.join('|').slice(0, INFLOW_MAX);
} catch (e) { return ''; }
}
/* imweb-header-top.html(업체 링크 추적)이 보관한 클릭 ID — 없으면 빈 배열 */
function readPartnerRefs() {
try {
var refs = window.MG_PARTNER && typeof window.MG_PARTNER.refs === 'function'
? window.MG_PARTNER.refs() : [];
return Array.isArray(refs) ? refs : [];
} catch (e) { return []; }
}
function recordApplication() {
if (!API_BASE || !PRODUCT_IDX_PATTERN.test(idx) ||
typeof window.fetch !== 'function') return;
applicationRequest = new Promise(function (resolve) {
whenMemberUid(function (memberUid) {
var body = { memberUid: memberUid, productIdx: idx };
if (name) body.productName = name.slice(0, PRODUCT_NAME_MAX);
var inflow = readInflow();
if (inflow) body.inflow = inflow;
var lastInflow = readLastInflow();
if (lastInflow) body.lastInflow = lastInflow;
var refs = readPartnerRefs();
if (refs.length) body.partnerRefs = refs;
window.fetch(API_BASE + '/applications', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'omit',
body: JSON.stringify(body)
}).then(function (res) {
return res.ok ? res.json() : null;
}).then(function (json) {
resolve(json && json.success && json.data ? json.data.id : null);
}).catch(function () { resolve(null); });
});
});
}
/* 오픈카톡 버튼 클릭 기록 — 서버는 처음 한 번만 남깁니다 */
function recordKakaoClick() {
if (!applicationRequest) return;
applicationRequest.then(function (id) {
if (!id) return;
window.fetch(API_BASE + '/applications/' + encodeURIComponent(id) +
'/kakao-clicks', { method: 'POST', credentials: 'omit' })
.catch(function () { });
});
}
function show() {
el.classList.add('is-shown');
// URL로 전달된 상품명을 '신청강의' 값에 채움
if (name) {
var valueEl = el.querySelector('.moneyguider-complete__value');
if (valueEl) valueEl.textContent = name;
}
/* 신청 기록 — 아래 카톡 버튼 처리가 중간에 끝날 수 있어 먼저 보냅니다 */
recordApplication();
/* (2) 방금 가입을 마치고 돌아온 경우에만 가입 전환 */
if (PIXEL.REGISTRATION && PIXEL.ON && ssGet(JOIN_FLAG)) {
whenPixelReady(function (ready) {
/* 끝내 안 뜨면(광고차단기 등) 표식을 남겨 둡니다.
어차피 못 보낼 상황이고, 지워봐야 손해만 봅니다. */
if (!ready) return;
ssDrop(JOIN_FLAG); /* 보내기 직전에 지웁니다 */
meta('CompleteRegistration', false, {
content_name: name || '', status: true
});
gads(PIXEL.GOOGLE_SIGNUP);
ga('sign_up', { method: 'imweb', content_name: name || '' });
});
}
/* (1) 오픈카톡 입장 버튼 클릭 → 광고 전환으로 쓸 이벤트
버튼이 target="_blank" 라 이 페이지가 그대로 남습니다.
덕분에 이벤트가 전송될 시간이 확보됩니다. 같은 탭에서
열도록 바꾸시면 전송 전에 페이지가 떠나 유실될 수 있습니다.
preventDefault 를 하지 않습니다. 링크는 평소대로 열립니다. */
var kakao = el.querySelector('.moneyguider-complete__kakao');
if (kakao) {
/* 상품번호로 오픈채팅방 주소를 고릅니다.
등록된 게 없으면 KAKAO_DEFAULT, 그것도 비어 있으면
버튼을 숨깁니다. */
var link = (Object.prototype.hasOwnProperty.call(KAKAO_LINKS, idx)
? KAKAO_LINKS[idx] : '') || KAKAO_DEFAULT;
if (!link) {
kakao.style.display = 'none';
return;
}
kakao.setAttribute('href', link);
var sent = false;
function onKakao() {
/* 한 화면에서 여러 번 눌러도 한 번만 집계합니다.
target="_blank" 라 페이지가 그대로 남아 있어서
두세 번 누르는 일이 실제로 생깁니다. 그대로 두면
한 사람이 전환 여러 건으로 잡힙니다. */
if (sent) return;
sent = true;
recordKakaoClick();
/* content_ids 를 함께 보내면 나중에 광고 관리자에서
상품별로 전환을 쪼개 볼 수 있습니다. */
var p = { content_name: name || '', content_category: 'kakao_openchat' };
if (idx) p.content_ids = [idx];
meta(PIXEL.KAKAO_EVENT, PIXEL.KAKAO_CUSTOM, p);
gads(PIXEL.GOOGLE_KAKAO);
ga('kakao_openchat_join', { content_name: name || '' });
}
kakao.addEventListener('click', onKakao);
/* 가운데 버튼 클릭은 click 이 아니라 auxclick 으로 옵니다.
새 탭에서 열리는 건 똑같으므로 함께 집계합니다.
(Ctrl+클릭은 button 0 의 click 이라 위에서 이미 잡힙니다) */
kakao.addEventListener('auxclick', function (e) {
if (e.button === 1) onKakao();
});
}
}
if (!REQUIRE_LOGIN) { show(); return; }
/* ── 로그인 판별 ──────────────────────────────────────
detail-buy-btn.html 과 같은 방식입니다.
window.MEMBER_UID 로그인 회원의 식별자 (비로그인이면 빈 값)
_imweb_login_state 로그인 상태 쿠키 ('Y')
반환값 : 'in' | 'out' | 'unknown'
※ '확실히 로그아웃(out)' 일 때만 튕겨냅니다. 판별이 안 되면
(unknown) 화면을 보여줍니다. 반대로 하면 아임웹 스크립트가
늦게 뜬 순간 정상적으로 가입을 마친 사람까지 완료 화면을
못 보게 됩니다. */
function loginState() {
var uid = window.MEMBER_UID;
var m = /(?:^|;\s*)_imweb_login_state=([^;]*)/.exec(document.cookie || '');
if (uid || (m && m[1] === 'Y')) return 'in';
if (typeof uid === 'string') return 'out';
return 'unknown';
}
/* 가입 직후 돌아왔는데 판별이 한 박자 늦어 다시 튕겨내는
무한 왕복을 막습니다. 세션당 한 번만 튕겨냅니다. */
var ONCE_KEY = 'mg-apply-bounced';
function bouncedBefore() {
try { return window.sessionStorage.getItem(ONCE_KEY) === '1'; }
catch (e) { return false; } /* 시크릿 모드 등 - 막지 않습니다 */
}
function markBounced() {
try { window.sessionStorage.setItem(ONCE_KEY, '1'); } catch (e) { }
}
/* 아임웹 복귀 주소 형식 : encodeURIComponent(btoa(주소)) */
function backUrl(url) {
try { return encodeURIComponent(window.btoa(url)); }
catch (e) { return ''; }
}
/* 튕겨냅니다. 성공하면 true (= 완료 화면을 보여주지 않음) */
function bounce() {
if (bouncedBefore()) return false;
markBounced();
if (REDIRECT_TO !== 'join') {
/* replace 를 쓰는 이유 : 뒤로가기로 다시 /apply 에
들어와 또 튕기는 일이 없게 합니다. */
window.location.replace(REDIRECT_TO);
return true;
}
var back = backUrl(window.location.href);
if (!back) return false;
window.location.replace(JOIN_PAGE +
(JOIN_PAGE.indexOf('?') === -1 ? '?' : '&') +
'back_url=' + back);
return true;
}
/* 판별될 때까지 잠깐 기다립니다. 그동안 완료 화면은 숨은 채로
둡니다. 먼저 보여줬다가 튕겨내면 내용이 한 번 번쩍입니다. */
var waited = 0;
(function check() {
var state = loginState();
if (state === 'unknown' && waited < WAIT_MS) {
waited += POLL_MS;
window.setTimeout(check, POLL_MS);
return;
}
if (state === 'out' && bounce()) return;
show();
})();
})();
</script>
<!-- ===== 코드 끝 ===== -->