nodebb-plugin-niki-loyalty 1.3.1 → 1.3.3

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.
@@ -1,623 +0,0 @@
1
- <script src="https://unpkg.com/html5-qrcode" type="text/javascript"></script>
2
- <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>
3
- <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/locale/tr.min.js"></script>
4
-
5
- <style>
6
- @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
7
-
8
- body.niki-kasa-body {
9
- background-color: #000;
10
- margin: 0;
11
- padding: 0;
12
- overflow: hidden;
13
- }
14
-
15
- #content {
16
- padding: 0 !important;
17
- margin: 0 !important;
18
- width: 100% !important;
19
- background: #000;
20
- }
21
-
22
- .start-view {
23
- position: fixed;
24
- top: 0;
25
- left: 0;
26
- width: 100%;
27
- height: 100%;
28
- background: radial-gradient(1200px 700px at 50% 10%, rgba(197, 160, 101, 0.18), transparent 60%), linear-gradient(180deg, #0b0b0b, #111 55%, #0b0b0b);
29
- z-index: 100;
30
- color: #fff;
31
- display: flex;
32
- flex-direction: column;
33
- align-items: center;
34
- justify-content: center;
35
- font-family: 'Poppins', sans-serif;
36
- }
37
-
38
- .logo-box {
39
- width: 90px;
40
- height: 90px;
41
- border-radius: 50%;
42
- border: 3px solid #C5A065;
43
- display: flex;
44
- align-items: center;
45
- justify-content: center;
46
- margin-bottom: 30px;
47
- box-shadow: 0 0 30px rgba(197, 160, 101, 0.2);
48
- background: #000;
49
- }
50
-
51
- .btn-start {
52
- background: #C5A065;
53
- color: #000;
54
- padding: 18px 40px;
55
- border-radius: 14px;
56
- font-weight: 700;
57
- border: none;
58
- cursor: pointer;
59
- display: flex;
60
- align-items: center;
61
- gap: 10px;
62
- font-size: 16px;
63
- margin-bottom: 15px;
64
- width: 260px;
65
- justify-content: center;
66
- box-shadow: 0 5px 15px rgba(197, 160, 101, 0.3);
67
- }
68
-
69
- .btn-hist {
70
- background: #222;
71
- color: #fff;
72
- padding: 18px 40px;
73
- border-radius: 14px;
74
- font-weight: 600;
75
- border: 1px solid #333;
76
- cursor: pointer;
77
- display: flex;
78
- align-items: center;
79
- gap: 10px;
80
- font-size: 14px;
81
- width: 260px;
82
- justify-content: center;
83
- }
84
-
85
- .camera-ui {
86
- position: fixed;
87
- top: 0;
88
- left: 0;
89
- width: 100%;
90
- height: 100%;
91
- background: #000;
92
- display: none;
93
- z-index: 200;
94
- }
95
-
96
- #reader {
97
- width: 100% !important;
98
- height: 100% !important;
99
- border: none !important;
100
- }
101
-
102
- #reader video {
103
- object-fit: cover !important;
104
- width: 100% !important;
105
- height: 100% !important;
106
- }
107
-
108
- .session-bar-bg {
109
- position: absolute;
110
- top: 0;
111
- left: 0;
112
- width: 100%;
113
- height: 6px;
114
- background: rgba(255, 255, 255, 0.2);
115
- z-index: 220;
116
- }
117
-
118
- .session-bar-fill {
119
- height: 100%;
120
- background: #C5A065;
121
- width: 100%;
122
- transition: width 1s linear;
123
- }
124
-
125
- .top-ctrl {
126
- position: absolute;
127
- top: 20px;
128
- left: 0;
129
- width: 100%;
130
- padding: 0 20px;
131
- display: flex;
132
- justify-content: space-between;
133
- z-index: 230;
134
- box-sizing: border-box;
135
- }
136
-
137
- .badge-live {
138
- background: rgba(0, 0, 0, 0.6);
139
- color: #C5A065;
140
- padding: 6px 14px;
141
- border-radius: 20px;
142
- font-size: 12px;
143
- font-weight: 700;
144
- }
145
-
146
- .btn-x {
147
- background: rgba(255, 255, 255, 0.2);
148
- width: 40px;
149
- height: 40px;
150
- border-radius: 50%;
151
- border: none;
152
- color: #fff;
153
- cursor: pointer;
154
- font-size: 18px;
155
- display: flex;
156
- align-items: center;
157
- justify-content: center;
158
- }
159
-
160
- .scan-guide {
161
- position: absolute;
162
- top: 50%;
163
- left: 50%;
164
- transform: translate(-50%, -50%);
165
- width: 70vw;
166
- height: 70vw;
167
- max-width: 300px;
168
- max-height: 300px;
169
- box-shadow: 0 0 0 4000px rgba(0, 0, 0, 0.85);
170
- border: 2px solid rgba(255, 255, 255, 0.3);
171
- border-radius: 24px;
172
- pointer-events: none;
173
- z-index: 210;
174
- }
175
-
176
- .laser {
177
- width: 100%;
178
- height: 2px;
179
- background: #C5A065;
180
- position: absolute;
181
- top: 50%;
182
- box-shadow: 0 0 15px #C5A065;
183
- animation: scanAnim 2s infinite ease-in-out;
184
- }
185
-
186
- @keyframes scanAnim {
187
- 0% {
188
- top: 10%;
189
- opacity: 0;
190
- }
191
-
192
- 50% {
193
- opacity: 1;
194
- }
195
-
196
- 100% {
197
- top: 90%;
198
- opacity: 0;
199
- }
200
- }
201
-
202
- .scan-hint {
203
- position: absolute;
204
- bottom: 60px;
205
- width: 100%;
206
- text-align: center;
207
- color: #fff;
208
- font-size: 13px;
209
- opacity: 0.8;
210
- z-index: 220;
211
- }
212
-
213
- .res-modal {
214
- position: fixed;
215
- bottom: 0;
216
- left: 0;
217
- width: 100%;
218
- background: #1a1a1a;
219
- border-radius: 30px 30px 0 0;
220
- padding: 40px 20px;
221
- text-align: center;
222
- transform: translateY(110%);
223
- transition: transform 0.3s;
224
- z-index: 600;
225
- box-sizing: border-box;
226
- }
227
-
228
- .res-modal.active {
229
- transform: translateY(0);
230
- }
231
-
232
- .icon-big {
233
- font-size: 60px;
234
- margin-bottom: 15px;
235
- }
236
-
237
- .res-title {
238
- color: #fff;
239
- font-size: 24px;
240
- font-weight: 700;
241
- margin-bottom: 10px;
242
- }
243
-
244
- .res-msg {
245
- color: #aaa;
246
- font-size: 15px;
247
- margin-bottom: 30px;
248
- }
249
-
250
- .btn-new {
251
- background: #fff;
252
- color: #000;
253
- width: 100%;
254
- padding: 18px;
255
- border-radius: 16px;
256
- font-size: 16px;
257
- font-weight: 700;
258
- border: none;
259
- cursor: pointer;
260
- }
261
-
262
- .hist-modal {
263
- position: fixed;
264
- top: 0;
265
- left: 0;
266
- width: 100%;
267
- height: 100%;
268
- background: #111;
269
- z-index: 700;
270
- display: none;
271
- flex-direction: column;
272
- font-family: 'Poppins', sans-serif;
273
- }
274
-
275
- .hist-head {
276
- padding: 20px;
277
- background: #1a1a1a;
278
- color: #fff;
279
- display: flex;
280
- justify-content: space-between;
281
- align-items: center;
282
- border-bottom: 1px solid #333;
283
- }
284
-
285
- .hist-head-title {
286
- font-weight: 700;
287
- font-size: 18px;
288
- }
289
-
290
- .hist-body {
291
- flex: 1;
292
- overflow-y: auto;
293
- padding: 15px;
294
- }
295
-
296
- .hist-row {
297
- display: flex;
298
- align-items: center;
299
- padding: 15px;
300
- margin-bottom: 10px;
301
- border-radius: 12px;
302
- background: #1a1a1a;
303
- border: 1px solid #2a2a2a;
304
- }
305
-
306
- .hist-u-img {
307
- width: 50px;
308
- height: 50px;
309
- border-radius: 50%;
310
- margin-right: 15px;
311
- object-fit: cover;
312
- background: #333;
313
- }
314
-
315
- .hist-info {
316
- flex: 1;
317
- }
318
-
319
- .hist-u-name {
320
- display: block;
321
- font-weight: 700;
322
- font-size: 15px;
323
- color: #fff;
324
- margin-bottom: 4px;
325
- }
326
-
327
- .hist-time {
328
- font-size: 12px;
329
- color: #888;
330
- }
331
-
332
- .hist-val {
333
- background: rgba(198, 40, 40, 0.2);
334
- color: #ef5350;
335
- padding: 8px 14px;
336
- border-radius: 10px;
337
- font-size: 15px;
338
- font-weight: 700;
339
- }
340
-
341
- .hist-empty {
342
- text-align: center;
343
- padding: 60px 20px;
344
- color: #666;
345
- }
346
-
347
- .hist-empty i {
348
- font-size: 50px;
349
- margin-bottom: 15px;
350
- display: block;
351
- }
352
-
353
- .hist-loading {
354
- text-align: center;
355
- padding: 50px;
356
- color: #888;
357
- }
358
-
359
- .hist-loading .spinner {
360
- display: inline-block;
361
- width: 40px;
362
- height: 40px;
363
- border: 4px solid #333;
364
- border-top-color: #C5A065;
365
- border-radius: 50%;
366
- animation: spin 1s linear infinite;
367
- }
368
-
369
- @keyframes spin {
370
- to {
371
- transform: rotate(360deg);
372
- }
373
- }
374
-
375
- .btn-group {
376
- display: grid;
377
- gap: 12px;
378
- }
379
- </style>
380
-
381
- <div id="start-view" class="start-view">
382
- <div class="logo-box"><i class="fa fa-qrcode" style="font-size:45px; color:#C5A065;"></i></div>
383
- <h2 style="margin:0 0 5px; font-size:24px;">NIKI POS</h2>
384
- <p style="color:#888; font-size:13px; margin:0 0 40px;">Yetkili Personel Terminali</p>
385
- <div class="btn-group">
386
- <button class="btn-start" id="btn-start-session"><i class="fa fa-camera"></i> OTURUMU BAŞLAT</button>
387
- <button class="btn-hist" id="btn-open-history"><i class="fa fa-history"></i> İŞLEM GEÇMİŞİ</button>
388
- </div>
389
- </div>
390
-
391
- <div id="camera-ui" class="camera-ui">
392
- <div class="session-bar-bg">
393
- <div class="session-bar-fill" id="timer-bar"></div>
394
- </div>
395
- <div class="top-ctrl">
396
- <div class="badge-live">● CANLI</div>
397
- <button class="btn-x" id="btn-stop-session"><i class="fa fa-times"></i></button>
398
- </div>
399
- <div id="reader"></div>
400
- <div class="scan-guide">
401
- <div class="laser"></div>
402
- </div>
403
- <div class="scan-hint">QR Kodu karenin içine tutun</div>
404
- </div>
405
-
406
- <div id="res-view" class="res-modal">
407
- <div id="res-icon" class="icon-big"></div>
408
- <div id="res-title" class="res-title">...</div>
409
- <div id="res-msg" class="res-msg">...</div>
410
- <button class="btn-new" id="btn-resume-scan">YENİ İŞLEM</button>
411
- </div>
412
-
413
- <div id="hist-view" class="hist-modal">
414
- <div class="hist-head">
415
- <span class="hist-head-title"><i class="fa fa-history"></i> İŞLEM GEÇMİŞİ</span>
416
- <button id="btn-close-history"
417
- style="background:none; border:none; color:#fff; font-size:22px; cursor:pointer;"><i
418
- class="fa fa-times"></i></button>
419
- </div>
420
- <div class="hist-body" id="hist-list-container">
421
- <!-- Liste buraya yüklenecek -->
422
- </div>
423
- </div>
424
-
425
- <script>
426
- (function () {
427
- 'use strict';
428
-
429
- var html5QrCode = null;
430
- var isProcessing = false;
431
- var sessionTimer = null;
432
-
433
- moment.locale('tr');
434
-
435
- // DOM hazır olunca event listener'ları bağla
436
- $(document).ready(function () {
437
- console.log('[NIKI] Document ready, binding events...');
438
-
439
- // Yetki kontrolü
440
- if (typeof app !== 'undefined' && !app.user.isAdmin && !app.user.isGlobalMod) {
441
- $('#start-view').html('<div style="text-align:center;padding:50px;"><h3>⛔ Yetkisiz Giriş</h3></div>');
442
- return;
443
- }
444
-
445
- // Event Listeners
446
- $('#btn-start-session').on('click', initSession);
447
- $('#btn-stop-session').on('click', stopSession);
448
- $('#btn-resume-scan').on('click', resumeScan);
449
- $('#btn-open-history').on('click', openHistory);
450
- $('#btn-close-history').on('click', closeHistory);
451
-
452
- console.log('[NIKI] Events bound successfully');
453
- });
454
-
455
- function initSession() {
456
- console.log('[NIKI] Starting session...');
457
- $('#start-view').fadeOut(200);
458
- $('#camera-ui').fadeIn(300);
459
- startTimer(600);
460
-
461
- html5QrCode = new Html5Qrcode("reader");
462
-
463
- Html5Qrcode.getCameras().then(function (devices) {
464
- if (devices && devices.length) {
465
- var cameraId = devices[devices.length - 1].id;
466
- for (var i = 0; i < devices.length; i++) {
467
- var label = (devices[i].label || '').toLowerCase();
468
- if (label.indexOf('back') > -1 || label.indexOf('environment') > -1) {
469
- cameraId = devices[i].id;
470
- break;
471
- }
472
- }
473
- html5QrCode.start(cameraId, { fps: 15, aspectRatio: window.innerWidth / window.innerHeight }, onScanSuccess);
474
- } else {
475
- alert('Kamera bulunamadı!');
476
- stopSession();
477
- }
478
- }).catch(function (err) {
479
- alert('Kamera Hatası: ' + err);
480
- stopSession();
481
- });
482
- }
483
-
484
- function startTimer(duration) {
485
- var timeLeft = duration;
486
- $('#timer-bar').css('width', '100%');
487
- clearInterval(sessionTimer);
488
- sessionTimer = setInterval(function () {
489
- timeLeft--;
490
- $('#timer-bar').css('width', (timeLeft / duration * 100) + '%');
491
- if (timeLeft <= 0) {
492
- alert('Oturum doldu.');
493
- stopSession();
494
- }
495
- }, 1000);
496
- }
497
-
498
- function onScanSuccess(decodedText) {
499
- if (isProcessing) return;
500
- isProcessing = true;
501
- if (navigator.vibrate) navigator.vibrate(200);
502
- html5QrCode.pause();
503
-
504
- $.post('/api/niki-loyalty/scan-qr', { token: decodedText, _csrf: config.csrf_token }, function (res) {
505
- if (res.success) {
506
- $('#res-icon').html('<i class="fa fa-check-circle" style="color:#2E7D32"></i>');
507
- $('#res-title').text('ÖDEME ALINDI');
508
- $('#res-msg').html('<b>' + res.customer.username + '</b><br>' + res.cost + ' Puan Tahsil Edildi.');
509
- } else {
510
- $('#res-icon').html('<i class="fa fa-times-circle" style="color:#C62828"></i>');
511
- $('#res-title').text('HATA');
512
- $('#res-msg').text(res.message || 'Bilinmeyen hata');
513
- }
514
- $('#res-view').addClass('active');
515
- }).fail(function () {
516
- alert('Sunucu Hatası!');
517
- resumeScan();
518
- });
519
- }
520
-
521
- function resumeScan() {
522
- $('#res-view').removeClass('active');
523
- setTimeout(function () {
524
- isProcessing = false;
525
- if (html5QrCode) html5QrCode.resume();
526
- }, 300);
527
- }
528
-
529
- function stopSession() {
530
- clearInterval(sessionTimer);
531
- if (html5QrCode) {
532
- html5QrCode.stop().then(function () {
533
- html5QrCode.clear();
534
- }).catch(function () { });
535
- }
536
- $('#camera-ui').hide();
537
- $('#res-view').removeClass('active');
538
- $('#start-view').fadeIn(200);
539
- isProcessing = false;
540
- }
541
-
542
- // =============================
543
- // İŞLEM GEÇMİŞİ - ÇALIŞAN VERSİYON
544
- // =============================
545
-
546
- function openHistory() {
547
- console.log('[NIKI] Opening history...');
548
- $('#hist-view').css('display', 'flex');
549
- loadHistory();
550
- }
551
-
552
- function closeHistory() {
553
- $('#hist-view').hide();
554
- }
555
-
556
- function loadHistory() {
557
- var $container = $('#hist-list-container');
558
-
559
- // Loading göster
560
- $container.html('<div class="hist-loading"><div class="spinner"></div><br>Yükleniyor...</div>');
561
-
562
- console.log('[NIKI] Fetching history...');
563
-
564
- $.ajax({
565
- url: '/api/niki-loyalty/kasa-history',
566
- method: 'GET',
567
- dataType: 'json',
568
- success: function (response) {
569
- console.log('[NIKI] API response received:', response);
570
-
571
- var items = [];
572
-
573
- // Hem eski format (array) hem yeni format ({data:[]}) destekle
574
- if (Array.isArray(response)) {
575
- items = response;
576
- } else if (response && response.data && Array.isArray(response.data)) {
577
- items = response.data;
578
- }
579
-
580
- console.log('[NIKI] Items to render:', items.length);
581
-
582
- if (items.length === 0) {
583
- $container.html('<div class="hist-empty"><i class="fa fa-inbox"></i>İşlem bulunamadı</div>');
584
- return;
585
- }
586
-
587
- // Listeyi oluştur
588
- var html = '';
589
- for (var i = 0; i < items.length; i++) {
590
- var item = items[i];
591
- var timeAgo = moment(item.ts).fromNow();
592
- var customerName = item.cust || 'Bilinmeyen';
593
- var amount = item.amt || 250;
594
- var picture = item.picture || 'https://via.placeholder.com/50';
595
-
596
- html += '<div class="hist-row">' +
597
- '<img src="' + picture + '" class="hist-u-img" onerror="this.src=\'https://via.placeholder.com/50\'">' +
598
- '<div class="hist-info">' +
599
- '<span class="hist-u-name">' + customerName + '</span>' +
600
- '<span class="hist-time">' + timeAgo + '</span>' +
601
- '</div>' +
602
- '<div class="hist-val">-' + amount + '</div>' +
603
- '</div>';
604
- }
605
-
606
- $container.html(html);
607
- console.log('[NIKI] History rendered successfully!');
608
- },
609
- error: function (xhr, status, error) {
610
- console.error('[NIKI] API error:', error);
611
- $container.html('<div class="hist-empty"><i class="fa fa-exclamation-circle"></i>Yükleme hatası</div>');
612
- }
613
- });
614
- }
615
-
616
- // Sayfa değişince temizle
617
- $(window).on('action:ajaxify.start', function () {
618
- stopSession();
619
- closeHistory();
620
- });
621
-
622
- })();
623
- </script>