nodebb-plugin-niki-loyalty 1.5.0 → 1.5.5

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.
@@ -82,7 +82,6 @@ $(document).ready(function () {
82
82
  }, 300);
83
83
  }
84
84
 
85
- console.log('[Niki-Loyalty] Toast gösterildi:', message);
86
85
  }
87
86
 
88
87
  // Fonksiyonu global yap (Konsoldan test için)
@@ -105,10 +104,8 @@ $(document).ready(function () {
105
104
  if (data && typeof data.points !== 'undefined') {
106
105
  var points = Math.floor(data.points);
107
106
  $('#widget-user-points').text(points);
108
- console.log('[Niki-Loyalty] Widget puanı güncellendi:', points);
109
107
  }
110
108
  }).fail(function () {
111
- console.log('[Niki-Loyalty] Widget puanı yüklenemedi.');
112
109
  });
113
110
  }
114
111
  // Fonksiyonu global yap
@@ -147,7 +144,6 @@ $(document).ready(function () {
147
144
  if (response && response.success) {
148
145
  // Puan kazanıldı! Bildirim göster
149
146
  showNikiToast('Günlük giriş için <strong style="color:#ffd700">+' + response.earned + ' Puan</strong> kazandın! 👋');
150
- console.log('[Niki-Loyalty] Günlük giriş puanı alındı. Yeni Toplam:', response.total);
151
147
 
152
148
  // Widget'ı güncelle
153
149
  if (typeof updateSidebarWidget === 'function') {
@@ -158,7 +154,6 @@ $(document).ready(function () {
158
154
  localStorage.setItem(storageKey, today);
159
155
  }).fail(function () {
160
156
  // Hata durumunda sessizce devam et
161
- console.log('[Niki-Loyalty] Günlük giriş kontrolü başarısız.');
162
157
  });
163
158
  }
164
159
 
@@ -177,13 +172,11 @@ $(document).ready(function () {
177
172
  // ============================================================
178
173
  // Sadece 'topic' (konu) sayfasındaysak sayaç çalışsın.
179
174
  if (ajaxify.data && ajaxify.data.template && ajaxify.data.template.name === 'topic') {
180
- console.log('[Niki-Loyalty] Konu sayfası algılandı, sayaç başlatılıyor...');
181
175
 
182
176
  // 10 Dakikada bir tetikle (Günde 10 limit var backendde)
183
177
  heartbeatInterval = setInterval(function () {
184
178
  if (document.hidden) return; // Sekme aktif değilse sayma
185
179
 
186
- console.log('[Niki-Loyalty] 10dk doldu. Puan isteniyor...');
187
180
 
188
181
  $.post('/api/niki-loyalty/heartbeat', { _csrf: config.csrf_token }, function (response) {
189
182
  if (response && response.earned) {
@@ -191,11 +184,9 @@ $(document).ready(function () {
191
184
  if (typeof showNikiToast === 'function') {
192
185
  showNikiToast('Konu okuduğun için <strong style="color:#ffd700">+1 Puan</strong> kazandın! 🐈');
193
186
  }
194
- console.log('[Niki-Loyalty] Heartbeat başarılı. Yeni Puan:', response.total);
195
187
  // Widget'ı hemen güncelle
196
188
  updateSidebarWidget();
197
189
  } else {
198
- console.log('[Niki-Loyalty] Puan gelmedi (Günlük okuma limiti dolmuş olabilir).');
199
190
  }
200
191
  });
201
192
  }, 600000); // 10 Dakika = 600.000 ms
@@ -260,42 +251,59 @@ $(document).ready(function () {
260
251
  let dailyScore = parseFloat(data.dailyScore);
261
252
  let scoreText = Number.isInteger(dailyScore) ? dailyScore : dailyScore.toFixed(1);
262
253
  $('#widget-daily-text').text(scoreText + ' / ' + data.dailyCap);
254
+ // Hedef label'ını da dinamik güncelle
255
+ $('.progress-text .target').text('Hedef: ' + data.dailyCap);
263
256
 
264
- // 3. DETAYLI SAYAÇLAR (Counts)
265
- const c = data.counts || {}; // Backend'den gelen sayaç objesi
257
+ // 3. ACTIONS bilgisiyle puan ve limitleri dinamik güncelle
258
+ const act = data.actions || {};
259
+ const c = data.counts || {};
266
260
 
267
261
  // Helper: İlerleme Yazdırma Fonksiyonu
268
- function setProgress(id, current, max, rowId) {
262
+ function setProgress(id, current, max, rowId, rewardText) {
269
263
  current = parseInt(current || 0);
270
264
  const el = $('#' + id);
271
265
  const row = $('#' + rowId);
272
266
 
267
+ // Puan miktarını güncelle
268
+ if (rewardText) {
269
+ row.find('.item-reward').text(rewardText);
270
+ }
271
+
273
272
  if (current >= max) {
274
273
  el.html('<span style="color:#4caf50; font-weight:bold;">Tamamlandı ✅</span>');
275
- row.addClass('completed'); // CSS ile silikleştir
274
+ row.addClass('completed');
276
275
  } else {
277
- el.text(`${current}/${max} Tamamlandı`);
276
+ el.text(current + '/' + max + ' Tamamlandı');
278
277
  row.removeClass('completed');
279
278
  }
280
279
  }
281
280
 
282
- // Tek Tek Güncelle (library.js ACTIONS ile eşleştirildi)
283
- setProgress('w-count-new_topic', c.new_topic, 1, 'item-new-topic');
284
- setProgress('w-count-reply', c.reply, 2, 'item-reply');
285
- setProgress('w-count-read', c.read, 10, 'item-read');
281
+ // Dinamik: backend ACTIONS'dan puan ve limit bilgilerini al
282
+ var loginAct = act.login || { points: 5, limit: 1 };
283
+ var topicAct = act.new_topic || { points: 5, limit: 1 };
284
+ var replyAct = act.reply || { points: 5, limit: 2 };
285
+ var readAct = act.read || { points: 1, limit: 10 };
286
+ var likeGivenAct = act.like_given || { points: 2.5, limit: 2 };
287
+ var likeTakenAct = act.like_taken || { points: 5, limit: 2 };
286
288
 
287
- // Like (Alma ve Atma toplamı 4 limit demiştik, burada basitleştirip toplamı gösteriyoruz)
288
- // Backend'de like_given ve like_taken ayrı tutuluyor, ikisini toplayalım:
289
- const totalLike = (parseInt(c.like_given || 0) + parseInt(c.like_taken || 0));
290
- // Not: Like limiti aslında alma 2 + atma 2 = 4.
291
- // Kullanıcıya toplam 4 üzerinden göstermek kafa karıştırmaz.
292
- setProgress('w-count-like', totalLike, 4, 'item-like');
289
+ setProgress('w-count-new_topic', c.new_topic, topicAct.limit, 'item-new-topic', '+' + topicAct.points);
290
+ setProgress('w-count-reply', c.reply, replyAct.limit, 'item-reply', '+' + replyAct.points);
291
+ setProgress('w-count-read', c.read, readAct.limit, 'item-read', '+' + readAct.points);
293
292
 
294
- // Login (Zaten girmişse 1'dir)
293
+ // Like: toplam limit = like_given.limit + like_taken.limit
294
+ var totalLikeLimit = likeGivenAct.limit + likeTakenAct.limit;
295
+ var totalLike = (parseInt(c.like_given || 0) + parseInt(c.like_taken || 0));
296
+ setProgress('w-count-like', totalLike, totalLikeLimit, 'item-like', '+' + likeGivenAct.points + '/+' + likeTakenAct.points);
297
+
298
+ // Login
295
299
  if (c.login >= 1) {
296
300
  $('#w-count-login').html('<span style="color:#4caf50;">Alındı ✅</span>');
297
301
  $('#item-login').addClass('completed');
302
+ } else {
303
+ $('#w-count-login').text('Giriş Yapılmadı');
304
+ $('#item-login').removeClass('completed');
298
305
  }
306
+ $('#item-login .item-reward').text('+' + loginAct.points);
299
307
  });
300
308
  }
301
309
 
@@ -372,7 +380,6 @@ $(document).ready(function () {
372
380
  });
373
381
  });
374
382
  }).fail(function () {
375
- console.log('[Niki-Loyalty] Cüzdan verileri yüklenemedi.');
376
383
  });
377
384
  }
378
385
 
package/yeedek ADDED
@@ -0,0 +1,313 @@
1
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
2
+ <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
3
+
4
+ <style>
5
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;500;700;800&display=swap');
6
+
7
+ .niki-wallet-wrapper{
8
+ max-width:380px;margin:40px auto;background:#FFF;
9
+ border-radius:30px;box-shadow:0 20px 60px rgba(0,0,0,0.08);
10
+ overflow:hidden;text-align:center;position:relative;
11
+ font-family:'Poppins',sans-serif;border:1px solid #F0F0F0;
12
+ }
13
+
14
+ .niki-header-bg{background:#1a1a1a;height:140px;width:100%;position:absolute;top:0;left:0;border-radius:0 0 40px 40px;}
15
+ .niki-content{position:relative;z-index:1;padding:30px;padding-top:70px;}
16
+
17
+ .niki-avatar{width:110px;height:110px;border-radius:50%;background:#fff;margin:0 auto 15px;border:5px solid #fff;box-shadow:0 10px 30px rgba(0,0,0,0.1);display:flex;align-items:center;justify-content:center;position:relative;}
18
+ .niki-avatar img{width:100%;height:100%;object-fit:cover;border-radius:50%;}
19
+
20
+ .niki-balance-big{font-size:60px;font-weight:800;color:#1a1a1a;line-height:1;margin-bottom:5px;letter-spacing:-2px;}
21
+ .niki-label{color:#999;font-size:11px;font-weight:700;letter-spacing:2px;text-transform:uppercase;margin-bottom:25px;}
22
+
23
+ .niki-bar-box{background:#FAFAFA;border-radius:20px;padding:25px 20px 40px;margin:25px 0;border:1px solid #EEE;position:relative;}
24
+ .niki-track{background:#E0E0E0;height:12px;border-radius:10px;width:100%;margin:15px 0;position:relative;}
25
+ .niki-fill{height:100%;background:#C5A065;width:0%;transition:width 0.8s cubic-bezier(0.34, 1.56, 0.64, 1);border-radius:10px;box-shadow:0 0 10px rgba(197,160,101,0.4);}
26
+
27
+ .tier-dot{position:absolute;top:50%;transform:translate(-50%,-50%);width:12px;height:12px;background:#fff;border:3px solid #ccc;border-radius:50%;z-index:2;transition:all 0.3s;}
28
+ .tier-dot.reached{border-color:#C5A065;background:#C5A065;}
29
+
30
+ .tier-label{position:absolute;top:18px;transform:translateX(-50%);font-size:9px;color:#aaa;font-weight:600;width:60px;text-align:center;line-height:1.1;}
31
+ .tier-dot.reached + .tier-label{color:#C5A065;}
32
+
33
+ .niki-btn{
34
+ background:#1a1a1a;color:#fff;width:100%;padding:20px;
35
+ border-radius:18px;font-size:14px;font-weight:700;border:none;
36
+ cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;
37
+ transition:transform 0.2s, box-shadow 0.2s;
38
+ }
39
+ .niki-btn:active{transform:scale(0.95);}
40
+ .niki-btn:disabled{background:#E0E0E0;color:#AAA;cursor:not-allowed;}
41
+
42
+ .qr-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(10,10,10,0.95);z-index:10000;display:none;align-items:center;justify-content:center;backdrop-filter:blur(10px);}
43
+ .ticket-card{background:#fff;width:320px;border-radius:30px;overflow:hidden;text-align:center;position:relative;animation:cardPop 0.4s cubic-bezier(0.175,0.885,0.32,1.275);box-shadow:0 0 0 8px rgba(255,255,255,0.1),0 30px 80px rgba(0,0,0,0.8);}
44
+ @keyframes cardPop{from{transform:scale(0.8) translateY(50px);opacity:0;}to{transform:scale(1) translateY(0);opacity:1;}}
45
+
46
+ .ticket-top{background:#1a1a1a;padding:25px;color:#C5A065;font-weight:700;letter-spacing:2px;font-size:14px;}
47
+ .ticket-body{padding:40px 30px;position:relative;}
48
+ #qrcode{display:flex;justify-content:center;margin-bottom:30px;}
49
+ #qrcode img{border:10px solid #fff;border-radius:10px;box-shadow:0 10px 30px rgba(0,0,0,0.1);}
50
+ .timer-wrapper{width:100%;height:6px;background:#eee;border-radius:10px;overflow:hidden;margin-top:10px;}
51
+ .timer-bar{height:100%;background:#C5A065;width:100%;transition:width 1s linear;}
52
+ .timer-text{font-size:12px;color:#888;margin-top:8px;font-weight:600;}
53
+ .close-circle{position:absolute;top:15px;right:15px;width:30px;height:30px;background:rgba(255,255,255,0.2);border-radius:50%;color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;transition:background 0.2s;}
54
+ .close-circle:hover{background:rgba(255,255,255,0.4);}
55
+
56
+ .success-view{display:none;padding:40px 20px;}
57
+ .success-icon{width:100px;height:100px;background:#2E7D32;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:50px;margin:0 auto 20px;box-shadow:0 10px 30px rgba(46,125,50,0.3);animation:popCheck 0.5s ease;}
58
+ @keyframes popCheck{0%{transform:scale(0);}80%{transform:scale(1.1);}100%{transform:scale(1);}}
59
+ </style>
60
+
61
+ <div class="niki-wallet-wrapper" id="niki-wallet-root">
62
+ <div class="niki-header-bg"></div>
63
+
64
+ <div class="niki-content">
65
+ <div class="niki-avatar"><img src="" id="niki-logo-main"></div>
66
+ <div class="niki-balance-big" id="my-points">...</div>
67
+ <div class="niki-label">MEVCUT PUAN</div>
68
+
69
+ <div class="niki-bar-box">
70
+ <div style="display:flex; justify-content:space-between; font-size:12px; font-weight:700; color:#1a1a1a;">
71
+ <span>HEDEFLER</span><span id="target-txt">250</span>
72
+ </div>
73
+
74
+ <div class="niki-track">
75
+ <div class="niki-fill" id="prog-bar"></div>
76
+
77
+ <div class="tier-dot" id="dot-1" style="left:24%;"></div>
78
+ <div class="tier-label" style="left:24%;">Kurabiye</div>
79
+
80
+ <div class="tier-dot" id="dot-2" style="left:48%;"></div>
81
+ <div class="tier-label" style="left:48%;">%30 İnd.</div>
82
+
83
+ <div class="tier-dot" id="dot-3" style="left:72%;"></div>
84
+ <div class="tier-label" style="left:72%;">%60 İnd.</div>
85
+
86
+ <div class="tier-dot" id="dot-4" style="left:100%;"></div>
87
+ <div class="tier-label" style="left:100%;">Kahve</div>
88
+ </div>
89
+
90
+ <div style="font-size:10px; color:#aaa; margin-top:25px; text-align:center;">Ödüller Niki Kasasından alınır</div>
91
+ <div style="font-size:10px; color:#aaa; margin-top:25px; text-align:center;">Kurabiye Ödülü kahvenin yanında ücretsizdir</div>
92
+ </div>
93
+
94
+ <button id="btn-qr" class="niki-btn" onclick="openQR()" disabled>
95
+ <i class="fa fa-lock"></i> PUAN YETERSİZ
96
+ </button>
97
+
98
+ <div style="margin-top:25px; font-size:10px; color:#ccc;">
99
+ NIKI THE CAT COFFEE &copy; LOYALTY
100
+ </div>
101
+ </div>
102
+ </div>
103
+
104
+ <div id="modal-qr" class="qr-overlay">
105
+ <div class="ticket-card">
106
+ <div class="close-circle" onclick="closeQR()"><i class="fa fa-times"></i></div>
107
+
108
+ <div id="view-code">
109
+ <div class="ticket-top">KASAYA GÖSTERİNİZ</div>
110
+ <div class="ticket-body">
111
+ <div id="qrcode"></div>
112
+ <div style="font-size:14px; font-weight:700; color:#1a1a1a; margin-top:10px;">TARANIYOR...</div>
113
+ <div class="timer-wrapper"><div class="timer-bar" id="time-bar"></div></div>
114
+ <div class="timer-text" id="timer-txt">2:00</div>
115
+ </div>
116
+ </div>
117
+
118
+ <div id="view-success" class="success-view">
119
+ <div class="success-icon"><i class="fa fa-check"></i></div>
120
+ <h2 style="font-size:24px; color:#1a1a1a; margin-bottom:10px;">AFİYET OLSUN!</h2>
121
+ <p style="color:#888; font-size:14px; margin-bottom:20px;">Ödeme alındı, ödülün verildi.</p>
122
+ <button class="niki-btn" onclick="closeQR()" style="padding:15px; font-size:14px;">TAMAM</button>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <script>
128
+ const MAX_GOAL = 250;
129
+ const TIERS = [
130
+ { cost: 60, name: "Kurabiye" },
131
+ { cost: 120, name: "%30 İndirim" },
132
+ { cost: 180, name: "%60 İndirim" },
133
+ { cost: 250, name: "Ücretsiz Kahve" }
134
+ ];
135
+
136
+ let pollInterval = null;
137
+ let initialPoints = 0;
138
+
139
+ // ✅ tek seferde bir kere çalıştırmayı garanti etmek için
140
+ let walletBooted = false;
141
+
142
+ function resetDots() {
143
+ $('#dot-1,#dot-2,#dot-3,#dot-4').removeClass('reached');
144
+ }
145
+
146
+ function safeLoadData() {
147
+ // DOM hazır değilse kaç
148
+ if (!$('#niki-wallet-root').length) return;
149
+
150
+ const logoUrl = (config && config.relative_path ? config.relative_path : '') + "/plugins/nodebb-plugin-niki-loyalty/static/logo.png";
151
+ $("#niki-logo-main").attr("src", logoUrl);
152
+
153
+ $.get('/api/niki-loyalty/wallet-data')
154
+ .done(function(data) {
155
+ const p = parseInt(data && data.points, 10) || 0;
156
+ initialPoints = p;
157
+
158
+ $('#my-points').text(p);
159
+
160
+ resetDots();
161
+
162
+ let pct = (p / MAX_GOAL) * 100;
163
+ if (pct > 100) pct = 100;
164
+ $('#prog-bar').css('width', '0%');
165
+ setTimeout(() => { $('#prog-bar').css('width', pct + '%'); }, 60);
166
+
167
+ if (p >= 60) $('#dot-1').addClass('reached');
168
+ if (p >= 120) $('#dot-2').addClass('reached');
169
+ if (p >= 180) $('#dot-3').addClass('reached');
170
+ if (p >= 250) $('#dot-4').addClass('reached');
171
+
172
+ let bestReward = null;
173
+ for (let i = TIERS.length - 1; i >= 0; i--) {
174
+ if (p >= TIERS[i].cost) { bestReward = TIERS[i]; break; }
175
+ }
176
+
177
+ const btn = $('#btn-qr');
178
+ if (bestReward) {
179
+ btn.prop('disabled', false)
180
+ .css('background', '#1a1a1a')
181
+ .html('<i class="fa fa-qrcode"></i> KULLAN: ' + String(bestReward.name).toUpperCase());
182
+ } else {
183
+ btn.prop('disabled', true)
184
+ .css('background', '#E0E0E0')
185
+ .html('<i class="fa fa-lock"></i> ' + (60 - p) + ' PUAN EKSİK');
186
+ }
187
+ })
188
+ .fail(function() {
189
+ // sayfa geçiş anında ajaxify sırasında bazen patlıyor -> tekrar dene
190
+ setTimeout(() => {
191
+ if ($('#niki-wallet-root').length) safeLoadData();
192
+ }, 250);
193
+ });
194
+ }
195
+
196
+ function bootWallet() {
197
+ if (walletBooted) return;
198
+ walletBooted = true;
199
+
200
+ // ilk yükleme
201
+ safeLoadData();
202
+
203
+ // ajaxify her sayfa bitişinde: bu sayfadaysak yeniden yükle
204
+ $(window).on('action:ajaxify.end.nikiWallet', function() {
205
+ if (window.location.pathname.indexOf('niki-wallet') > -1) {
206
+ safeLoadData();
207
+ }
208
+ });
209
+
210
+ // bazı temalarda end gelmeyebiliyor -> content loaded'da da garanti
211
+ $(window).on('action:topic.loaded.nikiWallet action:widgets.loaded.nikiWallet action:ajaxify.contentLoaded.nikiWallet', function() {
212
+ if (window.location.pathname.indexOf('niki-wallet') > -1) {
213
+ safeLoadData();
214
+ }
215
+ });
216
+ }
217
+
218
+ function openQR() {
219
+ if(!confirm("Mevcut puanınla alabileceğin EN YÜKSEK ödül kullanılacak. Onaylıyor musun?")) return;
220
+
221
+ $.post('/api/niki-loyalty/generate-qr', { _csrf: config.csrf_token }, function(res) {
222
+ if (res && res.success) {
223
+ $('#modal-qr').fadeIn(200).css('display', 'flex');
224
+ $('#view-code').show(); $('#view-success').hide();
225
+ $('#qrcode').empty();
226
+
227
+ new QRCode(document.getElementById("qrcode"), {
228
+ text: res.token,
229
+ width: 260, height: 260,
230
+ colorDark : "#000000", colorLight : "#ffffff",
231
+ correctLevel : QRCode.CorrectLevel.L
232
+ });
233
+
234
+ startTimer(120);
235
+ startPolling();
236
+ } else {
237
+ app.alertError(res && res.message ? res.message : 'Hata');
238
+ }
239
+ });
240
+ }
241
+
242
+ function startPolling() {
243
+ clearInterval(pollInterval);
244
+ pollInterval = setInterval(() => {
245
+ // modal kapandıysa polling yapma
246
+ if (!$('#modal-qr').is(':visible')) { clearInterval(pollInterval); return; }
247
+
248
+ $.get('/api/niki-loyalty/wallet-data', function(data) {
249
+ const currentP = parseInt(data && data.points, 10) || 0;
250
+ if (currentP < initialPoints) {
251
+ clearInterval(pollInterval);
252
+ showSuccess();
253
+ }
254
+ });
255
+ }, 2000);
256
+ }
257
+
258
+ function showSuccess() {
259
+ $('#view-code').hide();
260
+ $('#view-success').fadeIn(300);
261
+ confetti({ particleCount: 150, spread: 70, origin: { y: 0.6 }, colors: ['#C5A065', '#1a1a1a', '#ffffff'] });
262
+ try { new Audio('https://freesound.org/data/previews/171/171671_2437358-lq.mp3').play(); } catch(e){}
263
+ }
264
+
265
+ function startTimer(duration) {
266
+ let timer = duration;
267
+ $('#time-bar').css('width', '100%');
268
+
269
+ const countdown = setInterval(function() {
270
+ if (!$('#modal-qr').is(':visible') || $('#view-success').is(':visible')) {
271
+ clearInterval(countdown); return;
272
+ }
273
+
274
+ const minutes = Math.floor(timer / 60);
275
+ const seconds = timer % 60;
276
+ $('#timer-txt').text(minutes + ":" + (seconds < 10 ? "0" + seconds : seconds));
277
+
278
+ const pct = (timer / duration) * 100;
279
+ $('#time-bar').css('width', pct + '%');
280
+
281
+ timer--;
282
+ if (timer < 0) {
283
+ clearInterval(countdown);
284
+ closeQR();
285
+ app.alertError("Süre doldu.");
286
+ }
287
+ }, 1000);
288
+ }
289
+
290
+ function closeQR() {
291
+ $('#modal-qr').fadeOut(200);
292
+ clearInterval(pollInterval);
293
+ pollInterval = null;
294
+
295
+ // modal kapandıktan sonra cüzdanı tekrar yükle
296
+ setTimeout(safeLoadData, 200);
297
+ }
298
+
299
+ // ✅ NodeBB ajaxify: sayfa bu template'e geldiğinde boot edelim
300
+ $(window).on('action:ajaxify.end', function() {
301
+ if (window.location.pathname.indexOf('niki-wallet') > -1) {
302
+ walletBooted = false; // route tekrar gelince boot tekrar çalışsın
303
+ bootWallet();
304
+ }
305
+ });
306
+
307
+ // ✅ ilk açılış (refresh / direkt url)
308
+ $(document).ready(function() {
309
+ if (window.location.pathname.indexOf('niki-wallet') > -1) {
310
+ bootWallet();
311
+ }
312
+ });
313
+ </script>