nodebb-plugin-niki-loyalty 1.0.1

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/.gitattributes ADDED
@@ -0,0 +1,22 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
3
+
4
+ # Custom for Visual Studio
5
+ *.cs diff=csharp
6
+ *.sln merge=union
7
+ *.csproj merge=union
8
+ *.vbproj merge=union
9
+ *.fsproj merge=union
10
+ *.dbproj merge=union
11
+
12
+ # Standard to msysgit
13
+ *.doc diff=astextplain
14
+ *.DOC diff=astextplain
15
+ *.docx diff=astextplain
16
+ *.DOCX diff=astextplain
17
+ *.dot diff=astextplain
18
+ *.DOT diff=astextplain
19
+ *.pdf diff=astextplain
20
+ *.PDF diff=astextplain
21
+ *.rtf diff=astextplain
22
+ *.RTF diff=astextplain
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 NodeBB Inc. <sales@nodebb.org>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # Quickstart Plugin for NodeBB
2
+
3
+ A starter kit for quickly creating NodeBB plugins. Comes with a pre-setup SCSS file, server side JS script with an `static:app.load` hook, and a client-side script. Most plugins need at least one of the above, so this ought to save you some time. For a full list of hooks have a look at our [wiki page](https://github.com/NodeBB/NodeBB/wiki/Hooks), and for more information about creating plugins please visit our [documentation portal](https://docs.nodebb.org/).
4
+
5
+ Fork this or copy it, and using your favourite text editor find and replace all instances of `nodebb-plugin-quickstart` with `nodebb-plugin-your-plugins-name`. Change the author's name in the LICENSE and package.json files.
6
+
7
+ ## Hello World
8
+
9
+ Really simple, just edit `public/lib/main.js` and paste in `console.log('hello world');`, and that's it!
10
+
11
+ ## Installation
12
+
13
+ npm install nodebb-plugin-quickstart
14
+
15
+ ## Screenshots
16
+
17
+ Don't forget to add screenshots!
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ extends: ['@commitlint/config-angular'],
5
+ rules: {
6
+ 'header-max-length': [1, 'always', 72],
7
+ 'type-enum': [
8
+ 2,
9
+ 'always',
10
+ [
11
+ 'breaking',
12
+ 'build',
13
+ 'chore',
14
+ 'ci',
15
+ 'docs',
16
+ 'feat',
17
+ 'fix',
18
+ 'perf',
19
+ 'refactor',
20
+ 'revert',
21
+ 'style',
22
+ 'test',
23
+ ],
24
+ ],
25
+ },
26
+ };
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+
3
+ import serverConfig from 'eslint-config-nodebb';
4
+ import publicConfig from 'eslint-config-nodebb/public';
5
+
6
+ export default [
7
+ ...publicConfig,
8
+ ...serverConfig,
9
+ ];
10
+
@@ -0,0 +1,3 @@
1
+ {
2
+ "info": "Dies ist eine Übersetzungszeichenkette, sie wird in die Sprache des Benutzers übersetzt. Siehe den Ordner 'languages' im Stammverzeichnis dieses Plugins."
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "info": "This is a translation string, it gets translated to the users language. See the languages folder in the root of this plugin."
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "info": "This is a translation string, it gets translated to the users language. See the languages folder in the root of this plugin."
3
+ }
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const Controllers = module.exports;
4
+
5
+ Controllers.renderAdminPage = function (req, res/* , next */) {
6
+ /*
7
+ Make sure the route matches your path to template exactly.
8
+
9
+ If your route was:
10
+ myforum.com/some/complex/route
11
+ your template should be:
12
+ templates/some/complex/route.tpl
13
+ and you would render it like so:
14
+ res.render('some/complex/route');
15
+ */
16
+
17
+ res.render('admin/plugins/quickstart', {
18
+ title: 'Quick Start',
19
+ });
20
+ };
package/library.js ADDED
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ const db = require.main.require('./src/database');
4
+ const user = require.main.require('./src/user');
5
+ const routeHelpers = require.main.require('./src/controllers/helpers');
6
+
7
+ const Plugin = {};
8
+
9
+ // --- AYARLAR ---
10
+ const SETTINGS = {
11
+ pointsPerHeartbeat: 5, // Her vuruşta kaç puan?
12
+ heartbeatInterval: 60, // Kaç saniyede bir (Client ile uyumlu olmalı)
13
+ dailyCap: 250 // Günlük maksimum puan (Örn: 50dk aktiflik)
14
+ };
15
+
16
+ Plugin.init = async function (params) {
17
+ const router = params.router;
18
+ const middleware = params.middleware;
19
+
20
+ // API: Kalp Atışı (Puan Kazanma)
21
+ router.post('/api/niki-loyalty/heartbeat', middleware.ensureLoggedIn, async (req, res) => {
22
+ const uid = req.uid;
23
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, ''); // 20251214
24
+
25
+ // 1. Günlük Limiti Kontrol Et
26
+ const dailyKey = `niki:daily:${uid}:${today}`;
27
+ const currentDailyScore = await db.getObjectField(dailyKey, 'score') || 0;
28
+
29
+ if (parseInt(currentDailyScore) >= SETTINGS.dailyCap) {
30
+ return res.json({ earned: false, reason: 'daily_cap' });
31
+ }
32
+
33
+ // 2. Puan Ver
34
+ await user.incrementUserFieldBy(uid, 'niki_points', SETTINGS.pointsPerHeartbeat);
35
+ await db.incrObjectFieldBy(dailyKey, 'score', SETTINGS.pointsPerHeartbeat);
36
+
37
+ // 3. Güncel Bakiyeyi Dön
38
+ const newBalance = await user.getUserField(uid, 'niki_points');
39
+ return res.json({
40
+ earned: true,
41
+ points: SETTINGS.pointsPerHeartbeat,
42
+ total: newBalance,
43
+ daily: parseInt(currentDailyScore) + SETTINGS.pointsPerHeartbeat
44
+ });
45
+ });
46
+
47
+ // SAYFA: Cüzdan (/niki-wallet)
48
+ routeHelpers.setupPageRoute(router, '/niki-wallet', middleware, [], async (req, res) => {
49
+ const uid = req.uid;
50
+ if (!uid) return res.redirect('/login');
51
+
52
+ const today = new Date().toISOString().slice(0, 10).replace(/-/g, '');
53
+
54
+ // Verileri Çek
55
+ const [userData, dailyData] = await Promise.all([
56
+ user.getUserFields(uid, ['username', 'userslug', 'picture', 'niki_points']),
57
+ db.getObject(`niki:daily:${uid}:${today}`)
58
+ ]);
59
+
60
+ const currentPoints = parseInt(userData.niki_points) || 0;
61
+ const dailyScore = parseInt(dailyData ? dailyData.score : 0) || 0;
62
+
63
+ // Yüzdelik Hesapla (Bar için)
64
+ let dailyPercent = (dailyScore / SETTINGS.dailyCap) * 100;
65
+ if (dailyPercent > 100) dailyPercent = 100;
66
+
67
+ res.render('niki-wallet', {
68
+ title: 'Niki Cüzdan',
69
+ points: currentPoints,
70
+ dailyScore: dailyScore,
71
+ dailyCap: SETTINGS.dailyCap,
72
+ dailyPercent: dailyPercent,
73
+ user: userData
74
+ });
75
+ });
76
+ };
77
+
78
+ Plugin.addScripts = async function (scripts) {
79
+ scripts.push('plugins/nodebb-plugin-niki-loyalty/static/lib/client.js');
80
+ return scripts;
81
+ };
82
+
83
+ Plugin.addNavigation = async function (nav) {
84
+ nav.push({
85
+ "route": "/niki-wallet",
86
+ "title": "Niki Cüzdan",
87
+ "enabled": true,
88
+ "iconClass": "fa-coffee",
89
+ "text": "Niki Cüzdan"
90
+ });
91
+ return nav;
92
+ };
93
+
94
+ module.exports = Plugin;
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "nodebb-plugin-niki-loyalty",
3
+ "version": "1.0.1",
4
+ "description": "Niki The Cat Coffee Loyalty System - Earn points while studying on IEU Forum.",
5
+ "main": "library.js",
6
+ "nbbpm": {
7
+ "compatibility": "^4.0.0"
8
+ },
9
+ "keywords": [
10
+ "nodebb",
11
+ "plugin",
12
+ "loyalty",
13
+ "gamification",
14
+ "points",
15
+ "niki"
16
+ ],
17
+ "author": {
18
+ "name": "IEU Forum Team",
19
+ "email": "admin@ieu.app"
20
+ },
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/sucreistaken/nodebb-plugin-niki-loyalty"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/sucreistaken/nodebb-plugin-niki-loyalty/issues"
28
+ },
29
+ "dependencies": {}
30
+ }
package/plugin.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "id": "nodebb-plugin-niki-loyalty",
3
+ "name": "Niki Loyalty System",
4
+ "description": "Öğrenciler ders çalışsın, kahve kazansın.",
5
+ "url": "https://forum.ieu.app",
6
+ "library": "./library.js",
7
+ "hooks": [
8
+ { "hook": "static:app.load", "method": "init" },
9
+ { "hook": "filter:scripts.get", "method": "addScripts" },
10
+ { "hook": "filter:navigation.available", "method": "addNavigation" },
11
+ { "hook": "action:topic.get", "method": "checkTopicVisit" }
12
+ ],
13
+ "scripts": [
14
+ "static/lib/client.js"
15
+ ],
16
+ "staticDirs": {
17
+ "static": "./static"
18
+ },
19
+ "templates": "./templates"
20
+ }
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ $(document).ready(function () {
4
+ /*
5
+ This file shows how admin page client-side javascript can be included via a plugin.
6
+ If you check `plugin.json`, you'll see that this file is listed under "acpScripts".
7
+ That array tells NodeBB which files to bundle into the minified javascript
8
+ that is served to the end user.
9
+
10
+ Some events you can elect to listen for:
11
+
12
+ $(document).ready(); Fired when the DOM is ready
13
+ $(window).on('action:ajaxify.end', function(data) { ... }); "data" contains "url"
14
+ */
15
+
16
+ console.log('nodebb-plugin-quickstart: acp-loaded');
17
+ // Note how this is shown in the console on the first load of every page in the ACP
18
+ });
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ This file is located in the "modules" block of plugin.json
5
+ It is only loaded when the user navigates to /admin/plugins/quickstart page
6
+ It is not bundled into the min file that is served on the first load of the page.
7
+ */
8
+
9
+ import { save, load } from 'settings';
10
+ import * as uploader from 'uploader';
11
+
12
+ export function init() {
13
+ handleSettingsForm();
14
+ setupUploader();
15
+ };
16
+
17
+ function handleSettingsForm() {
18
+ load('quickstart', $('.quickstart-settings'), function () {
19
+ setupColorInputs();
20
+ });
21
+
22
+ $('#save').on('click', () => {
23
+ save('quickstart', $('.quickstart-settings')); // pass in a function in the 3rd parameter to override the default success/failure handler
24
+ });
25
+ }
26
+
27
+ function setupColorInputs() {
28
+ var colorInputs = $('[data-settings="colorpicker"]');
29
+ colorInputs.on('change', updateColors);
30
+ updateColors();
31
+ }
32
+
33
+ function updateColors() {
34
+ $('#preview').css({
35
+ color: $('#color').val(),
36
+ 'background-color': $('#bgColor').val(),
37
+ });
38
+ }
39
+
40
+ function setupUploader() {
41
+ $('#content input[data-action="upload"]').each(function () {
42
+ var uploadBtn = $(this);
43
+ uploadBtn.on('click', function () {
44
+ uploader.show({
45
+ route: config.relative_path + '/api/admin/upload/file',
46
+ params: {
47
+ folder: 'quickstart',
48
+ },
49
+ accept: 'image/*',
50
+ }, function (image) {
51
+ $('#' + uploadBtn.attr('data-target')).val(image);
52
+ });
53
+ });
54
+ });
55
+ }
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * This file shows how client-side javascript can be included via a plugin.
5
+ * If you check `plugin.json`, you'll see that this file is listed under "scripts".
6
+ * That array tells NodeBB which files to bundle into the minified javascript
7
+ * that is served to the end user.
8
+ *
9
+ * There are two (standard) ways to wait for when NodeBB is ready.
10
+ * This one below executes when NodeBB reports it is ready...
11
+ */
12
+
13
+ (async () => {
14
+ const hooks = await app.require('hooks');
15
+
16
+ hooks.on('action:app.load', () => {
17
+ // called once when nbb has loaded
18
+ });
19
+
20
+ hooks.on('action:ajaxify.end', (/* data */) => {
21
+ // called everytime user navigates between pages including first load
22
+ });
23
+ })();
24
+
25
+ /**
26
+ * ... and this one reports when the DOM is loaded (but NodeBB might not be fully ready yet).
27
+ * For most cases, you'll want the one above.
28
+ */
29
+
30
+ $(document).ready(function () {
31
+ // ...
32
+ });
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ /*
4
+ This file is located in the "modules" block of plugin.json
5
+ It is only loaded when the user navigates to /quickstart page
6
+ It is not bundled into the min file that is served on the first load of the page.
7
+ */
8
+
9
+ define('forum/quickstart', function () {
10
+ var module = {};
11
+ module.init = function () {
12
+ $('#last-p').text('quickstart.js loaded!');
13
+ };
14
+ return module;
15
+ });
package/renovate.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": [
3
+ "config:recommended"
4
+ ]
5
+ }
@@ -0,0 +1 @@
1
+ /* Place any SASS in here */
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ $(document).ready(function () {
4
+ // --- NIKI LOGO URL ---
5
+ // Buraya Niki'nin transparent PNG logosunu koymalısın.
6
+ const NIKI_LOGO_URL = 'https://i.imgur.com/kXUe4M6.png'; // Örnek kedi logosu
7
+
8
+ // 1. Widget'ı Ekrana Bas (Eğer giriş yapmışsa)
9
+ if (app.user.uid > 0 && $('#niki-floating-widget').length === 0) {
10
+ const widgetHtml = `
11
+ <div id="niki-floating-widget">
12
+ <div class="niki-widget-content" onclick="ajaxify.go('niki-wallet')">
13
+ <img src="${NIKI_LOGO_URL}" class="niki-widget-logo" alt="Niki">
14
+ <div class="niki-widget-text">
15
+ <span class="niki-lbl">PUANIM</span>
16
+ <span class="niki-val" id="niki-live-points">...</span>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ `;
21
+ $('body').append(widgetHtml);
22
+
23
+ // Açılışta puanı çek
24
+ $.get('/api/user/' + app.user.userslug, function(data) {
25
+ $('#niki-live-points').text(data.niki_points || 0);
26
+ });
27
+ }
28
+
29
+ // 2. Aktiflik Takibi (Time-on-Site)
30
+ let activeSeconds = 0;
31
+ let isUserActive = false;
32
+ let idleTimer;
33
+
34
+ // Hareket algılayıcı
35
+ function resetIdleTimer() {
36
+ isUserActive = true;
37
+ clearTimeout(idleTimer);
38
+ idleTimer = setTimeout(() => { isUserActive = false; }, 30000); // 30sn hareketsizse dur
39
+ }
40
+ $(window).on('mousemove scroll keydown click', resetIdleTimer);
41
+
42
+ // Her saniye kontrol
43
+ setInterval(() => {
44
+ // Sadece Topic sayfalarındaysak say
45
+ if (ajaxify.data.template.topic && document.visibilityState === 'visible' && isUserActive) {
46
+ activeSeconds++;
47
+ }
48
+
49
+ // 60 saniye doldu mu?
50
+ if (activeSeconds >= 60) {
51
+ sendHeartbeat();
52
+ activeSeconds = 0;
53
+ }
54
+ }, 1000);
55
+
56
+ function sendHeartbeat() {
57
+ const topicId = ajaxify.data.tid;
58
+ $.post('/api/niki-loyalty/heartbeat', { tid: topicId, _csrf: config.csrf_token }, function(res) {
59
+ if (res.earned) {
60
+ // UI Güncelle
61
+ $('#niki-live-points').text(res.total);
62
+ showNikiToast(`+${res.points} Puan! ☕`);
63
+
64
+ // Widget'a ufak bir "bounce" efekti ver
65
+ $('#niki-floating-widget').addClass('niki-bounce');
66
+ setTimeout(() => $('#niki-floating-widget').removeClass('niki-bounce'), 500);
67
+ }
68
+ });
69
+ }
70
+
71
+ // Özel Bildirim (Toast)
72
+ function showNikiToast(msg) {
73
+ $('.niki-toast').remove();
74
+ const toast = $(`<div class="niki-toast"><i class="fa fa-paw"></i> ${msg}</div>`);
75
+ $('body').append(toast);
76
+ setTimeout(() => { toast.addClass('show'); }, 100);
77
+ setTimeout(() => {
78
+ toast.removeClass('show');
79
+ setTimeout(() => toast.remove(), 300);
80
+ }, 3000);
81
+ }
82
+ });
@@ -0,0 +1,5 @@
1
+ <h1>Hello!</h1>
2
+
3
+ <p>This file is served by nodebb at domain.com/assets/plugins/nodebb-plugin-quickstart/static/samplefile.html</p>
4
+
5
+ Check plugin.json for the "staticDirs" property if you want to change the path.
@@ -0,0 +1,206 @@
1
+ /* --- NIKI LOYALTY UI --- */
2
+
3
+ /* 1. Floating Widget (Sol Alt) */
4
+ #niki-floating-widget {
5
+ position: fixed;
6
+ bottom: 25px;
7
+ left: 25px;
8
+ z-index: 1050;
9
+ font-family: 'Poppins', sans-serif; /* Varsa sitenin fontu */
10
+ }
11
+
12
+ .niki-widget-content {
13
+ background: rgba(255, 255, 255, 0.9);
14
+ backdrop-filter: blur(10px);
15
+ border: 1px solid rgba(78, 52, 46, 0.1);
16
+ padding: 6px 16px 6px 6px;
17
+ border-radius: 40px;
18
+ display: flex;
19
+ align-items: center;
20
+ gap: 10px;
21
+ box-shadow: 0 8px 20px rgba(78, 52, 46, 0.15);
22
+ cursor: pointer;
23
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
24
+ }
25
+
26
+ .niki-widget-content:hover {
27
+ transform: translateY(-3px);
28
+ box-shadow: 0 12px 25px rgba(78, 52, 46, 0.25);
29
+ background: #fff;
30
+ }
31
+
32
+ .niki-widget-logo {
33
+ width: 36px;
34
+ height: 36px;
35
+ border-radius: 50%;
36
+ background: #EFEBE9;
37
+ padding: 2px;
38
+ object-fit: cover;
39
+ }
40
+
41
+ .niki-widget-text {
42
+ display: flex;
43
+ flex-direction: column;
44
+ line-height: 1.1;
45
+ }
46
+
47
+ .niki-lbl {
48
+ font-size: 9px;
49
+ font-weight: 700;
50
+ color: #8D6E63;
51
+ letter-spacing: 0.5px;
52
+ }
53
+
54
+ .niki-val {
55
+ font-size: 15px;
56
+ font-weight: 800;
57
+ color: #4E342E;
58
+ }
59
+
60
+ /* Animasyon: Bounce */
61
+ .niki-bounce {
62
+ animation: niki-bounce-anim 0.5s;
63
+ }
64
+ @keyframes niki-bounce-anim {
65
+ 0%, 100% { transform: scale(1); }
66
+ 50% { transform: scale(1.15); }
67
+ }
68
+
69
+ /* 2. Toast Bildirimi */
70
+ .niki-toast {
71
+ position: fixed;
72
+ bottom: 90px;
73
+ left: 25px;
74
+ background: #4E342E;
75
+ color: #fff;
76
+ padding: 10px 20px;
77
+ border-radius: 12px;
78
+ font-size: 13px;
79
+ font-weight: 600;
80
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 8px;
84
+ z-index: 1060;
85
+ opacity: 0;
86
+ transform: translateY(20px) scale(0.9);
87
+ transition: all 0.3s ease;
88
+ pointer-events: none;
89
+ }
90
+
91
+ .niki-toast.show {
92
+ opacity: 1;
93
+ transform: translateY(0) scale(1);
94
+ }
95
+
96
+ /* 3. Cüzdan Sayfası */
97
+ .niki-wallet-wrapper {
98
+ max-width: 450px;
99
+ margin: 40px auto;
100
+ background: #fff;
101
+ border-radius: 24px;
102
+ box-shadow: 0 20px 60px rgba(0,0,0,0.08);
103
+ overflow: hidden;
104
+ text-align: center;
105
+ position: relative;
106
+ border: 1px solid #f0f0f0;
107
+ }
108
+
109
+ .niki-header-bg {
110
+ background: #4E342E;
111
+ height: 120px;
112
+ width: 100%;
113
+ position: absolute;
114
+ top: 0;
115
+ left: 0;
116
+ z-index: 0;
117
+ }
118
+
119
+ .niki-wallet-content {
120
+ position: relative;
121
+ z-index: 1;
122
+ padding: 30px;
123
+ padding-top: 60px;
124
+ }
125
+
126
+ .niki-wallet-avatar {
127
+ width: 110px;
128
+ height: 110px;
129
+ border-radius: 50%;
130
+ border: 5px solid #fff;
131
+ box-shadow: 0 10px 20px rgba(0,0,0,0.1);
132
+ margin: 0 auto 20px;
133
+ background: #fff;
134
+ display: flex; /* Logo ortalama */
135
+ align-items: center;
136
+ justify-content: center;
137
+ }
138
+
139
+ .niki-wallet-avatar img {
140
+ width: 100%;
141
+ height: 100%;
142
+ border-radius: 50%;
143
+ }
144
+
145
+ .niki-balance-big {
146
+ font-size: 42px;
147
+ font-weight: 900;
148
+ color: #4E342E;
149
+ margin-bottom: 5px;
150
+ }
151
+
152
+ .niki-balance-label {
153
+ color: #8D6E63;
154
+ font-size: 14px;
155
+ font-weight: 600;
156
+ text-transform: uppercase;
157
+ letter-spacing: 1px;
158
+ }
159
+
160
+ /* Progress Bar */
161
+ .niki-daily-stats {
162
+ background: #FAFAFA;
163
+ border-radius: 16px;
164
+ padding: 20px;
165
+ margin: 30px 0;
166
+ border: 1px solid #eee;
167
+ }
168
+
169
+ .niki-progress-track {
170
+ background: #E0E0E0;
171
+ height: 10px;
172
+ border-radius: 10px;
173
+ width: 100%;
174
+ margin: 10px 0;
175
+ overflow: hidden;
176
+ }
177
+
178
+ .niki-progress-fill {
179
+ height: 100%;
180
+ background: linear-gradient(90deg, #FFAB91, #FF7043);
181
+ border-radius: 10px;
182
+ transition: width 1s ease;
183
+ }
184
+
185
+ /* Buton */
186
+ .niki-btn-action {
187
+ background: #4E342E;
188
+ color: white;
189
+ width: 100%;
190
+ padding: 16px;
191
+ border-radius: 14px;
192
+ font-size: 16px;
193
+ font-weight: 700;
194
+ border: none;
195
+ cursor: pointer;
196
+ transition: transform 0.2s;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ gap: 10px;
201
+ }
202
+
203
+ .niki-btn-action:hover {
204
+ background: #3E2723;
205
+ transform: translateY(-2px);
206
+ }
@@ -0,0 +1,10 @@
1
+ <form>
2
+ <div class="mb-3">
3
+ <label class="form-label" for="name">Name</label>
4
+ <input type="text" id="name" name="name" class="form-control" placeholder="Name" />
5
+ </div>
6
+ <div class="mb-3">
7
+ <label class="form-label" for="description">Description</label>
8
+ <input type="text" id="description" name="description" class="form-control" placeholder="Description" />
9
+ </div>
10
+ </form>
@@ -0,0 +1,12 @@
1
+ <li data-type="item" class="list-group-item">
2
+ <div class="d-flex gap-2 justify-content-between align-items-start">
3
+ <div class="flex-grow-1">
4
+ <strong>{name}</strong><br />
5
+ <small>{description}</small>
6
+ </div>
7
+ <div class="d-flex gap-1 flex-nowrap">
8
+ <button type="button" data-type="edit" class="btn btn-sm btn-info">Edit</button>
9
+ <button type="button" data-type="remove" class="btn btn-sm btn-danger">Delete</button>
10
+ </div>
11
+ </div>
12
+ </li>
@@ -0,0 +1,68 @@
1
+ <div class="acp-page-container">
2
+ <!-- IMPORT admin/partials/settings/header.tpl -->
3
+
4
+ <div class="row m-0">
5
+ <div id="spy-container" class="col-12 col-md-8 px-0 mb-4" tabindex="0">
6
+ <form role="form" class="quickstart-settings">
7
+ <div class="mb-4">
8
+ <h5 class="fw-bold tracking-tight settings-header">General</h5>
9
+
10
+ <p class="lead">
11
+ Adjust these settings. You can then retrieve these settings in code via:
12
+ <br/><code>await meta.settings.get('quickstart');</code>
13
+ </p>
14
+ <div class="mb-3">
15
+ <label class="form-label" for="setting-1">Setting 1</label>
16
+ <input type="text" id="setting-1" name="setting-1" title="Setting 1" class="form-control" placeholder="Setting 1">
17
+ </div>
18
+ <div class="mb-3">
19
+ <label class="form-label" for="setting-2">Setting 2</label>
20
+ <input type="text" id="setting-2" name="setting-2" title="Setting 2" class="form-control" placeholder="Setting 2">
21
+ </div>
22
+
23
+ <div class="form-check form-switch">
24
+ <input type="checkbox" class="form-check-input" id="setting-3" name="setting-3">
25
+ <label for="setting-3" class="form-check-label">Setting 3</label>
26
+ </div>
27
+ </div>
28
+
29
+ <div class="mb-4">
30
+ <h5 class="fw-bold tracking-tight settings-header">Colors</h5>
31
+
32
+ <p class="alert" id="preview">
33
+ Here is some preview text. Use the inputs below to modify this alert's appearance.
34
+ </p>
35
+ <div class="mb-3 d-flex gap-2">
36
+ <label class="form-label" for="color">Foreground</label>
37
+ <input data-settings="colorpicker" type="color" id="color" name="color" title="Background Color" class="form-control p-1" placeholder="#ffffff" value="#ffffff" style="width: 64px;"/>
38
+ </div>
39
+ <div class="mb-3 d-flex gap-2">
40
+ <label class="form-label" for="bgColor">Background</label>
41
+ <input data-settings="colorpicker" type="color" id="bgColor" name="bgColor" title="Background Color" class="form-control p-1" placeholder="#000000" value="#000000" style="width: 64px;" />
42
+ </div>
43
+ </div>
44
+
45
+ <div class="mb-4">
46
+ <h5 class="fw-bold tracking-tight settings-header">Sorted List</h5>
47
+
48
+ <div class="mb-3" data-type="sorted-list" data-sorted-list="sample-list" data-item-template="admin/plugins/quickstart/partials/sorted-list/item" data-form-template="admin/plugins/quickstart/partials/sorted-list/form">
49
+ <ul data-type="list" class="list-group mb-2"></ul>
50
+ <button type="button" data-type="add" class="btn btn-info">Add Item</button>
51
+ </div>
52
+ </div>
53
+
54
+ <div>
55
+ <h5 class="fw-bold tracking-tight settings-header">Uploads</h5>
56
+
57
+ <label class="form-label" for="uploadedImage">Upload Image</label>
58
+ <div class="d-flex gap-1">
59
+ <input id="uploadedImage" name="uploadedImage" type="text" class="form-control" />
60
+ <input value="Upload" data-action="upload" data-target="uploadedImage" type="button" class="btn btn-light" />
61
+ </div>
62
+ </div>
63
+ </form>
64
+ </div>
65
+
66
+ <!-- IMPORT admin/partials/settings/toc.tpl -->
67
+ </div>
68
+ </div>
@@ -0,0 +1,35 @@
1
+ <div class="niki-wallet-wrapper">
2
+ <div class="niki-header-bg"></div>
3
+
4
+ <div class="niki-wallet-content">
5
+ <div class="niki-wallet-avatar">
6
+ <img src="https://i.imgur.com/kXUe4M6.png" alt="Niki">
7
+ </div>
8
+
9
+ <div class="niki-balance-label">Toplam Bakiye</div>
10
+ <div class="niki-balance-big">{points}</div>
11
+
12
+ <div class="niki-daily-stats">
13
+ <div style="display:flex; justify-content:space-between; font-size:12px; color:#888; font-weight:600;">
14
+ <span>Günlük Kazanım</span>
15
+ <span>{dailyScore} / {dailyCap}</span>
16
+ </div>
17
+
18
+ <div class="niki-progress-track">
19
+ <div class="niki-progress-fill" style="width: {dailyPercent}%;"></div>
20
+ </div>
21
+
22
+ <div style="font-size:11px; color:#aaa;">
23
+ Bugün daha fazla çalışarak limitini doldurabilirsin!
24
+ </div>
25
+ </div>
26
+
27
+ <button class="niki-btn-action">
28
+ <i class="fa fa-qrcode"></i> KAHVE AL (QR OLUŞTUR)
29
+ </button>
30
+
31
+ <p style="font-size:12px; color:#ccc; margin-top:15px;">
32
+ Niki The Cat Coffee &copy; Loyalty Program
33
+ </p>
34
+ </div>
35
+ </div>
@@ -0,0 +1,7 @@
1
+ <div class="card card-body text-bg-light">
2
+ <p>This is a custom page.</p>
3
+ <p>Your uid is {uid}!</p>
4
+ <p id="last-p"></p>
5
+ <hr/>
6
+ <p>[[quickstart:info]]</p>
7
+ </div>
package/test/.eslintrc ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "env": {
3
+ "mocha": true
4
+ },
5
+ "rules": {
6
+ "no-unused-vars": "off"
7
+ }
8
+ }
9
+
package/test/index.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * You can run these tests by executing `npx mocha test/plugins-installed.js`
3
+ * from the NodeBB root folder. The regular test runner will also run these
4
+ * tests.
5
+ *
6
+ * Keep in mind tests do not activate all plugins, so if you are testing
7
+ * hook listeners, socket.io, or mounted routes, you will need to add your
8
+ * plugin to `config.json`, e.g.
9
+ *
10
+ * {
11
+ * "test_plugins": [
12
+ * "nodebb-plugin-quickstart"
13
+ * ]
14
+ * }
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ /* globals describe, it, before */
20
+
21
+ const assert = require('assert');
22
+
23
+ const db = require.main.require('./test/mocks/databasemock');
24
+
25
+ describe('nodebb-plugin-quickstart', () => {
26
+ before(() => {
27
+ // Prepare for tests here
28
+ });
29
+
30
+ it('should pass', (done) => {
31
+ const actual = 'value';
32
+ const expected = 'value';
33
+ assert.strictEqual(actual, expected);
34
+ done();
35
+ });
36
+
37
+ it('should load config object', async () => { // Tests can be async functions too
38
+ const config = await db.getObject('config');
39
+ assert(config);
40
+ });
41
+ });