nodebb-plugin-niki-loyalty 1.0.25 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/library.js +183 -506
- package/package.json +1 -1
- package/static/lib/client.js +104 -448
- package/templates/niki-wallet.tpl +29 -164
package/package.json
CHANGED
package/static/lib/client.js
CHANGED
|
@@ -1,472 +1,128 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
$(document).ready(function () {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// QR ekranda kalma süresi (eski gibi) ✅ 2 dakika
|
|
20
|
-
const QR_TTL_SECONDS = 120;
|
|
21
|
-
|
|
22
|
-
// =========================================
|
|
23
|
-
// FLOATING WIDGET
|
|
24
|
-
// =========================================
|
|
25
|
-
const widgetHtml = `
|
|
26
|
-
<div id="niki-floating-widget" class="niki-hidden">
|
|
27
|
-
<div class="niki-widget-content" onclick="ajaxify.go('niki-wallet')">
|
|
28
|
-
<img src="${NIKI_LOGO_URL}" class="niki-widget-logo" alt="Niki">
|
|
29
|
-
<div class="niki-widget-text">
|
|
30
|
-
<span class="niki-lbl">PUANIM</span>
|
|
31
|
-
<span class="niki-val" id="niki-live-points">...</span>
|
|
4
|
+
// --- AYARLAR ---
|
|
5
|
+
// 1. Logo Ayarı (Senin çalışan linkin)
|
|
6
|
+
const NIKI_LOGO_URL = "https://i.ibb.co/nZvtpss/logo-placeholder.png";
|
|
7
|
+
|
|
8
|
+
// Widget HTML Şablonu
|
|
9
|
+
const widgetHtml = `
|
|
10
|
+
<div id="niki-floating-widget" class="niki-hidden">
|
|
11
|
+
<div class="niki-widget-content" onclick="ajaxify.go('niki-wallet')">
|
|
12
|
+
<img src="${NIKI_LOGO_URL}" class="niki-widget-logo" alt="Niki">
|
|
13
|
+
<div class="niki-widget-text">
|
|
14
|
+
<span class="niki-lbl">PUANIM</span>
|
|
15
|
+
<span class="niki-val" id="niki-live-points">...</span>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
32
18
|
</div>
|
|
33
|
-
|
|
34
|
-
</div>
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
function fixLogo() {
|
|
38
|
-
const img = document.querySelector("img.niki-widget-logo");
|
|
39
|
-
if (img && img.src !== NIKI_LOGO_URL) img.src = NIKI_LOGO_URL;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function showNikiToast(msg, kind) {
|
|
43
|
-
$('.niki-toast').remove();
|
|
44
|
-
const icon = (kind === 'err') ? 'fa-triangle-exclamation' : (kind === 'warn' ? 'fa-circle-info' : 'fa-paw');
|
|
45
|
-
const toast = $(`<div class="niki-toast niki-${kind || 'ok'}"><i class="fa ${icon}"></i> ${msg}</div>`);
|
|
46
|
-
$('body').append(toast);
|
|
47
|
-
setTimeout(() => toast.addClass('show'), 50);
|
|
48
|
-
setTimeout(() => {
|
|
49
|
-
toast.removeClass('show');
|
|
50
|
-
setTimeout(() => toast.remove(), 350);
|
|
51
|
-
}, 3000);
|
|
52
|
-
}
|
|
19
|
+
`;
|
|
53
20
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
$('#niki-floating-widget').removeClass('niki-hidden');
|
|
58
|
-
localStorage.setItem('niki_last_points', String(p));
|
|
21
|
+
// 1. Widget Başlatma ve Veri Yönetimi
|
|
22
|
+
function initNikiWidget() {
|
|
23
|
+
if (!app.user.uid || app.user.uid <= 0) return;
|
|
59
24
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
$(document).on('click', '#btn-qr-auto', function () {
|
|
65
|
-
$.post('/api/niki-loyalty/claim-auto', { _csrf: config.csrf_token }, function (res) {
|
|
66
|
-
if (!res || !res.success) {
|
|
67
|
-
app.alertError(res?.message || 'QR oluşturulamadı');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// modal aç (tpl’deki openCouponModal varsa onu çağır)
|
|
72
|
-
const title = `ÖDÜL • ${res.cost} PUAN`;
|
|
73
|
-
if (typeof window.openCouponModal === 'function') {
|
|
74
|
-
window.openCouponModal(title, res.token);
|
|
75
|
-
} else {
|
|
76
|
-
// client.js içindeki openQRModal fallback’ı varsa kullan
|
|
77
|
-
openQRModal({ token: res.token, title, ttlSeconds: 120 });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ✅ polling: puan düşünce success’e geç
|
|
81
|
-
initialPointsForQR = parseInt(localStorage.getItem('niki_last_points') || '0', 10) || null;
|
|
82
|
-
startPollingForSuccess();
|
|
83
|
-
});
|
|
84
|
-
});
|
|
25
|
+
// Widget yoksa ekle
|
|
26
|
+
if ($('#niki-floating-widget').length === 0) {
|
|
27
|
+
$('body').append(widgetHtml);
|
|
28
|
+
}
|
|
85
29
|
|
|
86
|
-
|
|
87
|
-
|
|
30
|
+
// --- HIZLI YÜKLEME (CACHE) ---
|
|
31
|
+
// Önce hafızadaki son puanı hemen göster (Bekletme yapmaz)
|
|
32
|
+
const cachedPoints = localStorage.getItem('niki_last_points');
|
|
33
|
+
if (cachedPoints !== null) {
|
|
34
|
+
$('#niki-live-points').text(cachedPoints);
|
|
35
|
+
$('#niki-floating-widget').removeClass('niki-hidden');
|
|
36
|
+
}
|
|
88
37
|
|
|
89
|
-
|
|
90
|
-
|
|
38
|
+
// Logo Kontrolü (Garanti olsun)
|
|
39
|
+
fixLogo();
|
|
40
|
+
|
|
41
|
+
// --- GÜNCEL VERİ ÇEKME ---
|
|
42
|
+
// Arka planda sunucuya sor: "Puan değişti mi?"
|
|
43
|
+
$.get('/api/niki-loyalty/wallet-data', function(data) {
|
|
44
|
+
const freshPoints = data.points || 0;
|
|
45
|
+
|
|
46
|
+
// Puanı güncelle
|
|
47
|
+
$('#niki-live-points').text(freshPoints);
|
|
48
|
+
$('#niki-floating-widget').removeClass('niki-hidden'); // İlk kez açılıyorsa göster
|
|
49
|
+
|
|
50
|
+
// Yeni puanı hafızaya at (Bir sonraki giriş için)
|
|
51
|
+
localStorage.setItem('niki_last_points', freshPoints);
|
|
52
|
+
|
|
53
|
+
// Logoyu tekrar kontrol et (Resim geç yüklendiyse)
|
|
54
|
+
fixLogo();
|
|
55
|
+
}).fail(function() {
|
|
56
|
+
// Hata olursa ve cache yoksa 0 yaz
|
|
57
|
+
if (cachedPoints === null) {
|
|
58
|
+
$('#niki-live-points').text('0');
|
|
59
|
+
$('#niki-floating-widget').removeClass('niki-hidden');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
91
62
|
}
|
|
92
63
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
64
|
+
// Logo Düzeltici (Senin çalışan kodun entegresi)
|
|
65
|
+
function fixLogo() {
|
|
66
|
+
const img = document.querySelector("img.niki-widget-logo");
|
|
67
|
+
if (img && img.src !== NIKI_LOGO_URL) {
|
|
68
|
+
img.src = NIKI_LOGO_URL;
|
|
69
|
+
}
|
|
97
70
|
}
|
|
98
71
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
$.get('/api/niki-loyalty/wallet-data', function (data) {
|
|
102
|
-
const freshPoints = data?.points || 0;
|
|
103
|
-
setPointsUI(freshPoints);
|
|
104
|
-
fixLogo();
|
|
105
|
-
|
|
106
|
-
// Wallet sayfasındaysa rewards render et
|
|
107
|
-
if (isWalletPage()) {
|
|
108
|
-
renderWalletFromData(data);
|
|
109
|
-
}
|
|
110
|
-
}).fail(function () {
|
|
111
|
-
if (cachedPoints === null) {
|
|
112
|
-
setPointsUI(0);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function isWalletPage() {
|
|
118
|
-
// nodebb template adı her projede değişebilir; pathname ile de kontrol edelim
|
|
119
|
-
const p = window.location.pathname || '';
|
|
120
|
-
return (ajaxify?.data?.template === 'niki-wallet') || p.indexOf('niki-wallet') > -1;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
initNikiWidget();
|
|
124
|
-
|
|
125
|
-
$(window).on('action:ajaxify.end', function () {
|
|
72
|
+
// Başlat
|
|
126
73
|
initNikiWidget();
|
|
127
|
-
setTimeout(fixLogo, 400);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// =========================================
|
|
131
|
-
// HEARTBEAT
|
|
132
|
-
// =========================================
|
|
133
|
-
let activeSeconds = 0;
|
|
134
|
-
let isUserActive = false;
|
|
135
|
-
let idleTimer;
|
|
136
74
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
$(window).on('mousemove scroll keydown click touchstart', resetIdleTimer);
|
|
75
|
+
// Sayfa Geçişlerinde Tekrar Çalıştır
|
|
76
|
+
$(window).on('action:ajaxify.end', function () {
|
|
77
|
+
initNikiWidget();
|
|
78
|
+
setTimeout(fixLogo, 500); // 0.5sn sonra son bir kontrol
|
|
79
|
+
});
|
|
143
80
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
81
|
+
// --- AKTİFLİK SİSTEMİ (Heartbeat) ---
|
|
82
|
+
let activeSeconds = 0;
|
|
83
|
+
let isUserActive = false;
|
|
84
|
+
let idleTimer;
|
|
85
|
+
|
|
86
|
+
function resetIdleTimer() {
|
|
87
|
+
isUserActive = true;
|
|
88
|
+
clearTimeout(idleTimer);
|
|
89
|
+
idleTimer = setTimeout(() => { isUserActive = false; }, 30000);
|
|
151
90
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
function sendHeartbeat() {
|
|
155
|
-
$.post('/api/niki-loyalty/heartbeat', { _csrf: config.csrf_token }, function (res) {
|
|
156
|
-
if (res && res.earned) {
|
|
157
|
-
setPointsUI(res.total);
|
|
158
|
-
showNikiToast(`+${res.points} Puan Kazandın! ⚡`, 'ok');
|
|
91
|
+
$(window).on('mousemove scroll keydown click touchstart', resetIdleTimer);
|
|
159
92
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// wallet açıksa güncelle
|
|
164
|
-
if (isWalletPage()) {
|
|
165
|
-
refreshWallet();
|
|
93
|
+
setInterval(() => {
|
|
94
|
+
if (ajaxify.data.template.topic && document.visibilityState === 'visible' && isUserActive) {
|
|
95
|
+
activeSeconds++;
|
|
166
96
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// =========================================
|
|
172
|
-
// WALLET: Rewards render + QR flow (2 dk)
|
|
173
|
-
// =========================================
|
|
174
|
-
let pollInterval = null;
|
|
175
|
-
let initialPointsForQR = null;
|
|
176
|
-
let timerInterval = null;
|
|
177
|
-
|
|
178
|
-
function refreshWallet() {
|
|
179
|
-
$.get('/api/niki-loyalty/wallet-data', function (data) {
|
|
180
|
-
setPointsUI(data?.points || 0);
|
|
181
|
-
renderWalletFromData(data);
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function renderWalletFromData(data) {
|
|
186
|
-
// 1) Bar (varsa)
|
|
187
|
-
try {
|
|
188
|
-
const p = parseInt(data?.points || 0, 10) || 0;
|
|
189
|
-
const goal = parseInt(data?.barMax || 250, 10) || 250;
|
|
190
|
-
const pct = Math.min(100, (p / goal) * 100);
|
|
191
|
-
$('#prog-bar').css('width', pct + '%');
|
|
192
|
-
$('#target-txt').text(`${p} / ${goal}`);
|
|
193
|
-
} catch (e) {}
|
|
194
|
-
|
|
195
|
-
// 2) Rewards container varsa bas
|
|
196
|
-
const $wrap = $('#niki-rewards');
|
|
197
|
-
if ($wrap.length === 0) return;
|
|
198
|
-
|
|
199
|
-
const points = parseInt(data?.points || 0, 10) || 0;
|
|
200
|
-
const rewards = Array.isArray(data?.rewards) && data.rewards.length ? data.rewards : REWARDS_FALLBACK.map(r => ({
|
|
201
|
-
id: r.id, at: r.at, title: r.title, unlocked: points >= r.at
|
|
202
|
-
}));
|
|
203
|
-
|
|
204
|
-
const cards = rewards.map(r => {
|
|
205
|
-
const at = parseInt(r.at || 0, 10) || 0;
|
|
206
|
-
const unlocked = points >= at;
|
|
207
|
-
|
|
208
|
-
const btnText = unlocked ? 'QR OLUŞTUR' : `${at - points} PUAN EKSİK`;
|
|
209
|
-
const btnDisabled = unlocked ? '' : 'disabled';
|
|
210
|
-
|
|
211
|
-
return `
|
|
212
|
-
<div class="niki-r-card ${unlocked ? 'on' : ''}" style="display:flex;align-items:center;justify-content:space-between;padding:12px 12px;border-radius:16px;background:#FAFAFA;border:1px solid #EEE;margin:10px 0;">
|
|
213
|
-
<div style="text-align:left;">
|
|
214
|
-
<div style="font-weight:800;color:#1a1a1a;font-size:13px;">${escapeHtml(r.title)}</div>
|
|
215
|
-
<div style="color:#888;font-size:11px;font-weight:700;margin-top:2px;">${at} puan</div>
|
|
216
|
-
</div>
|
|
217
|
-
<button class="niki-genqr-btn" data-reward="${escapeAttr(r.id)}" data-title="${escapeAttr(r.title)}" data-cost="${at}"
|
|
218
|
-
${btnDisabled}
|
|
219
|
-
style="border:none;border-radius:14px;padding:12px 12px;font-weight:900;font-size:12px;cursor:${unlocked ? 'pointer' : 'not-allowed'};background:${unlocked ? '#1a1a1a' : '#E0E0E0'};color:${unlocked ? '#fff' : '#999'};min-width:120px;">
|
|
220
|
-
${btnText}
|
|
221
|
-
</button>
|
|
222
|
-
</div>
|
|
223
|
-
`;
|
|
224
|
-
}).join('');
|
|
225
|
-
|
|
226
|
-
$wrap.html(`
|
|
227
|
-
<div style="font-size:11px;color:#888;text-align:left;margin:6px 0 10px;font-weight:700;">
|
|
228
|
-
QR oluşturunca puanın <b>kasada okutulunca</b> düşer. (2 dakika geçerli)
|
|
229
|
-
</div>
|
|
230
|
-
${cards}
|
|
231
|
-
`);
|
|
232
|
-
|
|
233
|
-
// click bind
|
|
234
|
-
$wrap.off('click.nikiGen').on('click.nikiGen', '.niki-genqr-btn:not([disabled])', function () {
|
|
235
|
-
const rewardId = $(this).data('reward');
|
|
236
|
-
const title = $(this).data('title') || 'Ödül';
|
|
237
|
-
const cost = parseInt($(this).data('cost') || 0, 10) || 0;
|
|
238
|
-
|
|
239
|
-
generateRewardQR({ rewardId, title, cost });
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function generateRewardQR({ rewardId, title, cost }) {
|
|
244
|
-
// qr modal varsa onu kullan, yoksa basit popup yap
|
|
245
|
-
if (!rewardId) return;
|
|
246
|
-
|
|
247
|
-
// güvenli: o anda puan snapshot
|
|
248
|
-
$.get('/api/niki-loyalty/wallet-data', function (data) {
|
|
249
|
-
const points = parseInt(data?.points || 0, 10) || 0;
|
|
250
|
-
if (points < cost) {
|
|
251
|
-
showNikiToast('Yetersiz puan', 'warn');
|
|
252
|
-
refreshWallet();
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (!confirm(`${title} için QR oluşturulsun mu? (2 dakika geçerli)`)) return;
|
|
257
|
-
|
|
258
|
-
// ✅ Claim = sadece token üret (puan düşmez), okutunca düşer
|
|
259
|
-
$.post('/api/niki-loyalty/claim', { rewardId, _csrf: config.csrf_token }, function (res) {
|
|
260
|
-
if (!res || !res.success || !res.token) {
|
|
261
|
-
showNikiToast(res?.message || 'QR oluşturulamadı', 'err');
|
|
262
|
-
return;
|
|
97
|
+
if (activeSeconds >= 60) {
|
|
98
|
+
sendHeartbeat();
|
|
99
|
+
activeSeconds = 0;
|
|
263
100
|
}
|
|
101
|
+
}, 1000);
|
|
264
102
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
103
|
+
function sendHeartbeat() {
|
|
104
|
+
$.post('/api/niki-loyalty/heartbeat', { _csrf: config.csrf_token }, function(res) {
|
|
105
|
+
if (res.earned) {
|
|
106
|
+
// Puanı güncelle
|
|
107
|
+
$('#niki-live-points').text(res.total);
|
|
108
|
+
// Hafızayı da güncelle
|
|
109
|
+
localStorage.setItem('niki_last_points', res.total);
|
|
110
|
+
|
|
111
|
+
showNikiToast(`+${res.points} Puan Kazandın! ☕`);
|
|
112
|
+
$('#niki-floating-widget').addClass('niki-bounce');
|
|
113
|
+
setTimeout(() => $('#niki-floating-widget').removeClass('niki-bounce'), 500);
|
|
114
|
+
}
|
|
270
115
|
});
|
|
271
|
-
|
|
272
|
-
startPollingForSuccess();
|
|
273
|
-
}).fail(function () {
|
|
274
|
-
showNikiToast('Sunucu hatası (claim)', 'err');
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function openQRModal({ token, title, ttlSeconds }) {
|
|
280
|
-
// Wallet tpl'nin modalını destekle
|
|
281
|
-
const $modal = $('#modal-qr');
|
|
282
|
-
const hasTplModal = $modal.length > 0;
|
|
283
|
-
|
|
284
|
-
if (!hasTplModal) {
|
|
285
|
-
// fallback: hızlı modal enjekte
|
|
286
|
-
injectFallbackQRModal();
|
|
287
116
|
}
|
|
288
117
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
$(qrEl).empty();
|
|
299
|
-
|
|
300
|
-
if (typeof QRCode !== 'undefined') {
|
|
301
|
-
// Büyük, okunaklı QR
|
|
302
|
-
new QRCode(qrEl, {
|
|
303
|
-
text: String(token),
|
|
304
|
-
width: 260,
|
|
305
|
-
height: 260,
|
|
306
|
-
colorDark: "#000000",
|
|
307
|
-
colorLight: "#ffffff",
|
|
308
|
-
correctLevel: QRCode.CorrectLevel.L
|
|
309
|
-
});
|
|
310
|
-
} else {
|
|
311
|
-
$(qrEl).html(`<div style="font-weight:900;word-break:break-all;">${escapeHtml(token)}</div>`);
|
|
312
|
-
}
|
|
118
|
+
function showNikiToast(msg) {
|
|
119
|
+
$('.niki-toast').remove();
|
|
120
|
+
const toast = $(`<div class="niki-toast"><i class="fa fa-paw"></i> ${msg}</div>`);
|
|
121
|
+
$('body').append(toast);
|
|
122
|
+
setTimeout(() => { toast.addClass('show'); }, 100);
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
toast.removeClass('show');
|
|
125
|
+
setTimeout(() => toast.remove(), 3000);
|
|
126
|
+
}, 3000);
|
|
313
127
|
}
|
|
314
|
-
|
|
315
|
-
// modal aç
|
|
316
|
-
$('#modal-qr').fadeIn(200).css('display', 'flex');
|
|
317
|
-
|
|
318
|
-
// timer
|
|
319
|
-
startTimer(ttlSeconds);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function startTimer(durationSec) {
|
|
323
|
-
clearInterval(timerInterval);
|
|
324
|
-
let timer = durationSec;
|
|
325
|
-
const total = durationSec;
|
|
326
|
-
|
|
327
|
-
$('#time-bar').css('width', '100%');
|
|
328
|
-
$('#timer-txt').text(formatTime(timer));
|
|
329
|
-
|
|
330
|
-
timerInterval = setInterval(function () {
|
|
331
|
-
// modal kapanırsa dur
|
|
332
|
-
if (!$('#modal-qr').is(':visible') || $('#view-success').is(':visible')) {
|
|
333
|
-
clearInterval(timerInterval);
|
|
334
|
-
timerInterval = null;
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const pct = Math.max(0, (timer / total) * 100);
|
|
339
|
-
$('#time-bar').css('width', pct + '%');
|
|
340
|
-
$('#timer-txt').text(formatTime(timer));
|
|
341
|
-
|
|
342
|
-
timer -= 1;
|
|
343
|
-
if (timer < 0) {
|
|
344
|
-
clearInterval(timerInterval);
|
|
345
|
-
timerInterval = null;
|
|
346
|
-
closeQRModal();
|
|
347
|
-
showNikiToast('Süre doldu, kod geçersiz olabilir.', 'warn');
|
|
348
|
-
}
|
|
349
|
-
}, 1000);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function startPollingForSuccess() {
|
|
353
|
-
clearInterval(pollInterval);
|
|
354
|
-
|
|
355
|
-
pollInterval = setInterval(() => {
|
|
356
|
-
// modal açık değilse polling durdur
|
|
357
|
-
if (!$('#modal-qr').is(':visible')) {
|
|
358
|
-
clearInterval(pollInterval);
|
|
359
|
-
pollInterval = null;
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// ✅ Eski gibi: puan düşerse -> kasa okutmuş demektir
|
|
364
|
-
$.get('/api/niki-loyalty/wallet-data', function (data) {
|
|
365
|
-
const currentP = parseInt(data?.points || 0, 10) || 0;
|
|
366
|
-
|
|
367
|
-
// Puan düşmüşse başarı
|
|
368
|
-
if (initialPointsForQR != null && currentP < initialPointsForQR) {
|
|
369
|
-
clearInterval(pollInterval);
|
|
370
|
-
pollInterval = null;
|
|
371
|
-
|
|
372
|
-
showSuccessView();
|
|
373
|
-
setPointsUI(currentP);
|
|
374
|
-
// reward list refresh
|
|
375
|
-
renderWalletFromData(data);
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
}, 2000);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function showSuccessView() {
|
|
382
|
-
$('#view-code').hide();
|
|
383
|
-
$('#view-success').fadeIn(200);
|
|
384
|
-
|
|
385
|
-
// confetti varsa patlat
|
|
386
|
-
try {
|
|
387
|
-
if (typeof confetti !== 'undefined') {
|
|
388
|
-
confetti({ particleCount: 140, spread: 70, origin: { y: 0.6 } });
|
|
389
|
-
}
|
|
390
|
-
} catch (e) {}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// tpl'nin closeQR fonksiyonu varsa onu çağır, yoksa kendin kapat
|
|
394
|
-
window.closeQR = window.closeQR || function () { closeQRModal(); };
|
|
395
|
-
|
|
396
|
-
function closeQRModal() {
|
|
397
|
-
$('#modal-qr').fadeOut(200);
|
|
398
|
-
clearInterval(pollInterval);
|
|
399
|
-
pollInterval = null;
|
|
400
|
-
clearInterval(timerInterval);
|
|
401
|
-
timerInterval = null;
|
|
402
|
-
initialPointsForQR = null;
|
|
403
|
-
|
|
404
|
-
// wallet refresh
|
|
405
|
-
if (isWalletPage()) {
|
|
406
|
-
setTimeout(refreshWallet, 400);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function injectFallbackQRModal() {
|
|
411
|
-
if ($('#modal-qr').length) return;
|
|
412
|
-
|
|
413
|
-
$('body').append(`
|
|
414
|
-
<div id="modal-qr" class="qr-overlay" style="position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(10,10,10,.92);z-index:10000;">
|
|
415
|
-
<div class="ticket-card" style="background:#fff;width:320px;border-radius:26px;overflow:hidden;position:relative;">
|
|
416
|
-
<div class="close-circle" onclick="closeQR()" style="position:absolute;top:12px;right:12px;width:30px;height:30px;border-radius:50%;background:rgba(0,0,0,.08);display:flex;align-items:center;justify-content:center;cursor:pointer;">
|
|
417
|
-
<i class="fa fa-times"></i>
|
|
418
|
-
</div>
|
|
419
|
-
|
|
420
|
-
<div id="view-code">
|
|
421
|
-
<div class="ticket-top" id="ticket-title" style="background:#111;padding:22px;color:#C5A065;font-weight:900;letter-spacing:2px;font-size:12px;text-align:center;">
|
|
422
|
-
KASAYA GÖSTERİNİZ
|
|
423
|
-
</div>
|
|
424
|
-
<div class="ticket-body" style="padding:30px 22px;text-align:center;">
|
|
425
|
-
<div id="qrcode" style="display:flex;justify-content:center;margin-bottom:16px;"></div>
|
|
426
|
-
<div id="ticket-sub" style="font-size:13px;font-weight:900;color:#111;margin-top:4px;">QR hazır</div>
|
|
427
|
-
<div class="timer-wrapper" style="width:100%;height:6px;background:#eee;border-radius:10px;overflow:hidden;margin-top:12px;">
|
|
428
|
-
<div class="timer-bar" id="time-bar" style="height:100%;background:#C5A065;width:100%;"></div>
|
|
429
|
-
</div>
|
|
430
|
-
<div class="timer-text" id="timer-txt" style="font-size:12px;color:#777;margin-top:8px;font-weight:800;">2:00</div>
|
|
431
|
-
</div>
|
|
432
|
-
</div>
|
|
433
|
-
|
|
434
|
-
<div id="view-success" class="success-view" style="display:none;padding:34px 18px;text-align:center;">
|
|
435
|
-
<div class="success-icon" style="width:92px;height:92px;margin:0 auto 16px;border-radius:50%;background:#2E7D32;color:#fff;display:flex;align-items:center;justify-content:center;font-size:44px;">
|
|
436
|
-
<i class="fa fa-check"></i>
|
|
437
|
-
</div>
|
|
438
|
-
<div style="font-size:20px;font-weight:900;color:#111;">AFİYET OLSUN!</div>
|
|
439
|
-
<div style="font-size:13px;color:#777;margin-top:6px;">Kasa onayı alındı.</div>
|
|
440
|
-
<button class="niki-btn" onclick="closeQR()" style="margin-top:16px;background:#111;color:#fff;width:100%;padding:14px;border-radius:16px;border:none;font-weight:900;">
|
|
441
|
-
TAMAM
|
|
442
|
-
</button>
|
|
443
|
-
</div>
|
|
444
|
-
</div>
|
|
445
|
-
</div>
|
|
446
|
-
`);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function formatTime(sec) {
|
|
450
|
-
sec = Math.max(0, parseInt(sec, 10) || 0);
|
|
451
|
-
const m = Math.floor(sec / 60);
|
|
452
|
-
const s = sec % 60;
|
|
453
|
-
return `${m}:${s < 10 ? '0' + s : s}`;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function escapeHtml(str) {
|
|
457
|
-
return String(str || '')
|
|
458
|
-
.replaceAll('&', '&')
|
|
459
|
-
.replaceAll('<', '<')
|
|
460
|
-
.replaceAll('>', '>')
|
|
461
|
-
.replaceAll('"', '"')
|
|
462
|
-
.replaceAll("'", ''');
|
|
463
|
-
}
|
|
464
|
-
function escapeAttr(str) {
|
|
465
|
-
return escapeHtml(str).replaceAll(' ', '');
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Wallet sayfasına ilk girişte render
|
|
469
|
-
if (isWalletPage()) {
|
|
470
|
-
refreshWallet();
|
|
471
|
-
}
|
|
472
|
-
});
|
|
128
|
+
});
|