nodebb-plugin-sso-biogrenci 1.0.3 → 1.0.4

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/image.png CHANGED
Binary file
package/library.js CHANGED
@@ -16,7 +16,7 @@ const constants = {
16
16
  name: 'biogrenci',
17
17
  displayName: "bi'öğrenci",
18
18
  icon: 'fa-graduation-cap',
19
- scope: 'openid profile email student_status education opportunities.read',
19
+ scope: 'openid profile email student_status student opportunities.read',
20
20
  authorizationURL: 'https://biogrenci.com/oauth/authorize',
21
21
  tokenURL: 'https://biogrenci.com/api/oauth/token',
22
22
  userInfoURL: 'https://biogrenci.com/api/oauth/userinfo',
@@ -214,6 +214,8 @@ plugin.getStrategy = async function (strategies) {
214
214
  return strategies;
215
215
  }
216
216
 
217
+ try {
218
+
217
219
  const callbackURL = nconf.get('url') + '/auth/biogrenci/callback';
218
220
  winston.info(`${LOG_PREFIX} Registering OAuth2 strategy, callbackURL=%s`, callbackURL);
219
221
 
@@ -371,6 +373,11 @@ plugin.getStrategy = async function (strategies) {
371
373
  });
372
374
 
373
375
  winston.info(`${LOG_PREFIX} Strategy added to list. Total strategies: %d`, strategies.length);
376
+
377
+ } catch (err) {
378
+ winston.error(`${LOG_PREFIX} getStrategy FAILED: %s\n%s`, err.message, err.stack);
379
+ }
380
+
374
381
  return strategies;
375
382
  };
376
383
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-sso-biogrenci",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "bi'öğrenci OAuth2 SSO login & öğrenci indirimleri for NodeBB",
5
5
  "main": "library.js",
6
6
  "keywords": [
@@ -2,187 +2,227 @@
2
2
 
3
3
  define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
4
4
  var Page = {};
5
-
6
- var currentPage = 1;
7
- var currentType = '';
8
- var currentSort = 'featured';
9
- var currentSearch = '';
5
+ var state = {
6
+ page: 1,
7
+ type: '',
8
+ sort: 'featured',
9
+ search: '',
10
+ };
10
11
  var searchTimeout = null;
12
+ var allBrands = new Set();
11
13
 
12
14
  Page.init = function () {
13
15
  console.log('[sso-biogrenci] Firsatlar page init');
14
16
  loadOpportunities();
17
+ bindEvents();
18
+ };
15
19
 
16
- // Filter buttons
17
- $('.biogrenci-filter').on('click', function () {
18
- $('.biogrenci-filter').removeClass('active');
20
+ function bindEvents() {
21
+ // Filters
22
+ $(document).on('click', '.bio-chip', function () {
23
+ $('.bio-chip').removeClass('active');
19
24
  $(this).addClass('active');
20
- currentType = $(this).attr('data-type');
21
- currentPage = 1;
22
- console.log('[sso-biogrenci] Filter changed: type=%s', currentType);
25
+ state.type = $(this).attr('data-type');
26
+ state.page = 1;
23
27
  loadOpportunities();
24
28
  });
25
29
 
26
30
  // Sort
27
31
  $('#biogrenci-sort').on('change', function () {
28
- currentSort = $(this).val();
29
- currentPage = 1;
30
- console.log('[sso-biogrenci] Sort changed: %s', currentSort);
32
+ state.sort = $(this).val();
33
+ state.page = 1;
31
34
  loadOpportunities();
32
35
  });
33
36
 
34
- // Search with debounce
37
+ // Search
35
38
  $('#biogrenci-search').on('input', function () {
36
39
  clearTimeout(searchTimeout);
37
40
  var val = $(this).val();
38
41
  searchTimeout = setTimeout(function () {
39
- currentSearch = val;
40
- currentPage = 1;
41
- console.log('[sso-biogrenci] Search: %s', currentSearch);
42
+ state.search = val;
43
+ state.page = 1;
42
44
  loadOpportunities();
43
- }, 400);
45
+ }, 350);
44
46
  });
45
47
 
46
- // Pagination
47
- $('#biogrenci-prev').on('click', function () {
48
- if (currentPage > 1) {
49
- currentPage--;
48
+ // ESC to clear search
49
+ $('#biogrenci-search').on('keydown', function (e) {
50
+ if (e.key === 'Escape') {
51
+ $(this).val('');
52
+ state.search = '';
53
+ state.page = 1;
50
54
  loadOpportunities();
51
55
  }
52
56
  });
53
57
 
58
+ // Pagination
59
+ $('#biogrenci-prev').on('click', function () {
60
+ if (state.page > 1) { state.page--; loadOpportunities(); }
61
+ });
54
62
  $('#biogrenci-next').on('click', function () {
55
- currentPage++;
63
+ state.page++;
56
64
  loadOpportunities();
57
65
  });
58
- };
66
+
67
+ // Claim (delegated)
68
+ $(document).on('click', '.bio-card-cta', function () {
69
+ claimOpportunity($(this));
70
+ });
71
+
72
+ // Copy (delegated)
73
+ $(document).on('click', '.bio-copy-btn', function () {
74
+ var code = $(this).attr('data-code');
75
+ navigator.clipboard.writeText(code);
76
+ alerts.success('Kopyalandı!');
77
+ $(this).html('<i class="fa fa-check"></i>');
78
+ var $btn = $(this);
79
+ setTimeout(function () { $btn.html('<i class="fa fa-copy"></i>'); }, 2000);
80
+ });
81
+ }
59
82
 
60
83
  function loadOpportunities() {
61
84
  var $list = $('#biogrenci-list');
62
85
  $list.html(
63
- '<div class="biogrenci-loading">' +
64
- '<div class="biogrenci-spinner"></div>' +
86
+ '<div class="bio-loading-state">' +
87
+ '<div class="bio-loader"></div>' +
65
88
  '<p>Fırsatlar yükleniyor...</p>' +
66
89
  '</div>'
67
90
  );
68
91
 
69
92
  var params = new URLSearchParams({
70
- page: currentPage,
93
+ page: state.page,
71
94
  per_page: 20,
72
- sort: currentSort,
95
+ sort: state.sort,
73
96
  });
74
- if (currentType) params.set('type', currentType);
75
- if (currentSearch) params.set('search', currentSearch);
97
+ if (state.type) params.set('type', state.type);
98
+ if (state.search) params.set('search', state.search);
76
99
 
77
100
  var url = config.relative_path + '/api/biogrenci/firsatlar?' + params.toString();
78
- console.log('[sso-biogrenci] Fetching opportunities: %s', url);
101
+ console.log('[sso-biogrenci] Fetching:', url);
79
102
 
80
103
  fetch(url)
81
- .then(function (r) {
82
- console.log('[sso-biogrenci] API response status: %d', r.status);
83
- return r.json();
84
- })
104
+ .then(function (r) { return r.json(); })
85
105
  .then(function (res) {
86
- console.log('[sso-biogrenci] API response:', JSON.stringify(res).substring(0, 500));
87
- console.log('[sso-biogrenci] Response keys:', Object.keys(res));
88
-
89
106
  if (res.error) {
90
- console.error('[sso-biogrenci] API error:', res.error);
91
- $list.html(
92
- '<div class="biogrenci-empty biogrenci-error">' +
93
- '<i class="fa fa-exclamation-triangle fa-3x"></i>' +
94
- '<h4>Fırsatlar yüklenirken hata oluştu</h4>' +
95
- '<p>' + res.error + '</p>' +
96
- '</div>'
97
- );
107
+ showError(res.error);
98
108
  return;
99
109
  }
100
110
 
101
- // API returns { success, data: { opportunities: [...], meta: {...} } }
102
111
  var payload = res.data || res;
103
112
  var items = payload.opportunities || payload.data || payload.items || payload;
104
113
  if (!Array.isArray(items)) items = [];
105
114
  var meta = payload.meta || res.meta || {};
106
115
 
107
- console.log('[sso-biogrenci] Items count: %d', items.length);
116
+ console.log('[sso-biogrenci] Got %d items', items.length);
108
117
 
109
- if (!items || !items.length) {
110
- $list.html(
111
- '<div class="biogrenci-empty">' +
112
- '<i class="fa fa-search fa-3x"></i>' +
113
- '<h4>Fırsat bulunamadı</h4>' +
114
- '<p>Farklı filtreler deneyebilirsiniz</p>' +
115
- '</div>'
116
- );
117
- $('#biogrenci-pagination').hide();
118
+ // Update stats
119
+ items.forEach(function (item) {
120
+ var brandName = item.brand ? item.brand.name : item.brand_name;
121
+ if (brandName) allBrands.add(brandName);
122
+ });
123
+ $('#bio-stat-total span').text(meta.total || items.length);
124
+ $('#bio-stat-brands span').text(allBrands.size || '--');
125
+
126
+ if (!items.length) {
127
+ showEmpty();
118
128
  return;
119
129
  }
120
130
 
121
- renderOpportunities(items);
131
+ renderCards(items);
122
132
  renderPagination(meta);
123
133
  })
124
134
  .catch(function (err) {
125
- console.error('[sso-biogrenci] Fetch error:', err);
126
- $list.html(
127
- '<div class="biogrenci-empty biogrenci-error">' +
128
- '<i class="fa fa-exclamation-triangle fa-3x"></i>' +
129
- '<h4>Fırsatlar yüklenirken hata oluştu</h4>' +
130
- '<p>' + err.message + '</p>' +
131
- '</div>'
132
- );
135
+ console.error('[sso-biogrenci] Error:', err);
136
+ showError(err.message);
133
137
  });
134
138
  }
135
139
 
136
- function renderOpportunities(items) {
137
- var $list = $('#biogrenci-list');
140
+ function showEmpty() {
141
+ $('#biogrenci-list').html(
142
+ '<div class="bio-empty-state">' +
143
+ '<div class="bio-empty-icon"><i class="fa fa-gift"></i></div>' +
144
+ '<h3>Fırsat bulunamadı</h3>' +
145
+ '<p>Filtre veya aramayı değiştirerek tekrar deneyin</p>' +
146
+ '</div>'
147
+ );
148
+ $('#biogrenci-pagination').hide();
149
+ }
150
+
151
+ function showError(msg) {
152
+ $('#biogrenci-list').html(
153
+ '<div class="bio-empty-state bio-error-state">' +
154
+ '<div class="bio-empty-icon"><i class="fa fa-exclamation-circle"></i></div>' +
155
+ '<h3>Bir hata oluştu</h3>' +
156
+ '<p>' + (msg || 'Lütfen tekrar deneyin') + '</p>' +
157
+ '</div>'
158
+ );
159
+ }
160
+
161
+ function getDaysLeft(endDate) {
162
+ if (!endDate) return null;
163
+ var now = new Date();
164
+ var end = new Date(endDate);
165
+ var diff = Math.ceil((end - now) / (1000 * 60 * 60 * 24));
166
+ return diff > 0 ? diff : null;
167
+ }
168
+
169
+ function renderCards(items) {
138
170
  var html = items.map(function (item) {
171
+ var brandName = item.brand ? item.brand.name : (item.brand_name || '');
139
172
  var typeLabel = item.type === 'kupon' ? 'Kupon' : item.type === 'qr' ? 'QR Kod' : 'Link';
140
- var typeBadge = item.type === 'kupon' ? 'badge-kupon' : item.type === 'qr' ? 'badge-qr' : 'badge-affiliate';
173
+ var typeClass = 'bio-type-' + (item.type || 'kupon');
174
+ var typeIcon = item.type === 'kupon' ? 'fa-ticket' : item.type === 'qr' ? 'fa-qrcode' : 'fa-external-link';
175
+ var daysLeft = getDaysLeft(item.end_date);
176
+ var btnText = item.button_text || 'Fırsatı Al';
141
177
 
142
178
  return (
143
- '<div class="biogrenci-card">' +
144
- (item.image
145
- ? '<div class="biogrenci-card-img"><img src="' + item.image + '" alt="' + (item.brand_name || '') + '" loading="lazy"></div>'
146
- : '<div class="biogrenci-card-img biogrenci-card-img-placeholder"><i class="fa fa-gift fa-3x"></i></div>') +
147
- '<div class="biogrenci-card-body">' +
148
- '<div class="biogrenci-card-top">' +
149
- '<span class="biogrenci-badge ' + typeBadge + '">' + typeLabel + '</span>' +
150
- (item.brand_name ? '<span class="biogrenci-brand">' + item.brand_name + '</span>' : '') +
179
+ '<div class="bio-card' + (item.is_featured ? ' bio-card-featured' : '') + '">' +
180
+ '<div class="bio-card-visual">' +
181
+ (item.image
182
+ ? '<img src="' + item.image + '" alt="' + brandName + '" loading="lazy">'
183
+ : '<div class="bio-card-placeholder"><i class="fa fa-gift"></i></div>') +
184
+ '<div class="bio-card-overlay">' +
185
+ '<span class="bio-type-badge ' + typeClass + '"><i class="fa ' + typeIcon + '"></i> ' + typeLabel + '</span>' +
186
+ (item.is_featured ? '<span class="bio-featured-badge"><i class="fa fa-star"></i></span>' : '') +
151
187
  '</div>' +
152
- '<h3 class="biogrenci-card-title">' + item.title + '</h3>' +
153
- (item.description ? '<p class="biogrenci-card-desc">' + item.description + '</p>' : '') +
154
- '<button class="btn biogrenci-claim-btn" data-id="' + item.id + '">' +
155
- '<i class="fa fa-hand-pointer-o"></i> Fırsatı Al' +
156
- '</button>' +
188
+ (daysLeft !== null
189
+ ? '<div class="bio-card-countdown">' +
190
+ '<i class="fa fa-clock-o"></i> ' +
191
+ (daysLeft <= 3 ? '<strong>Son ' + daysLeft + ' gün!</strong>' : daysLeft + ' gün kaldı') +
192
+ '</div>'
193
+ : '') +
194
+ '</div>' +
195
+ '<div class="bio-card-content">' +
196
+ (brandName ? '<span class="bio-card-brand">' + brandName + '</span>' : '') +
197
+ '<h3 class="bio-card-title">' + item.title + '</h3>' +
198
+ (item.description ? '<p class="bio-card-desc">' + item.description + '</p>' : '') +
199
+ '<button class="bio-card-cta" data-id="' + item.id + '">' + btnText + '</button>' +
157
200
  '</div>' +
158
201
  '</div>'
159
202
  );
160
203
  }).join('');
161
204
 
162
- $list.html(html);
163
-
164
- $list.find('.biogrenci-claim-btn').on('click', function () {
165
- claimOpportunity($(this));
166
- });
205
+ $('#biogrenci-list').html(html);
167
206
  }
168
207
 
169
208
  function renderPagination(meta) {
170
209
  var $el = $('#biogrenci-pagination');
171
- if (!meta.total || meta.total <= 20) {
210
+ var total = meta.total || 0;
211
+ if (total <= 20) {
172
212
  $el.hide();
173
213
  return;
174
214
  }
175
215
  $el.show();
176
- var totalPages = Math.ceil(meta.total / 20);
177
- $('#biogrenci-page-info').text(currentPage + ' / ' + totalPages);
178
- $('#biogrenci-prev').prop('disabled', currentPage <= 1);
179
- $('#biogrenci-next').prop('disabled', currentPage >= totalPages);
216
+ var totalPages = Math.ceil(total / 20);
217
+ $('#biogrenci-page-info').text(state.page + ' / ' + totalPages);
218
+ $('#biogrenci-prev').prop('disabled', state.page <= 1);
219
+ $('#biogrenci-next').prop('disabled', state.page >= totalPages);
180
220
  }
181
221
 
182
222
  function claimOpportunity($btn) {
183
223
  var id = $btn.attr('data-id');
184
- $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Alınıyor...');
185
- console.log('[sso-biogrenci] Claiming opportunity id=%s', id);
224
+ var originalText = $btn.text();
225
+ $btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i>');
186
226
 
187
227
  fetch(config.relative_path + '/api/biogrenci/firsatlar/' + id + '/claim', {
188
228
  method: 'POST',
@@ -193,41 +233,37 @@ define('forum/biogrenci-firsatlar', ['alerts'], function (alerts) {
193
233
  })
194
234
  .then(function (r) { return r.json(); })
195
235
  .then(function (res) {
196
- console.log('[sso-biogrenci] Claim response:', res);
197
236
  if (res.error) {
198
237
  alerts.error(res.error);
199
- $btn.prop('disabled', false).html('<i class="fa fa-hand-pointer-o"></i> Fırsatı Al');
238
+ $btn.prop('disabled', false).text(originalText);
200
239
  return;
201
240
  }
202
241
 
203
242
  var data = res.data || res;
204
243
  if (data.coupon_code) {
205
244
  $btn.replaceWith(
206
- '<div class="biogrenci-coupon-result">' +
207
- '<code class="biogrenci-coupon-code">' + data.coupon_code + '</code>' +
208
- '<button class="btn btn-sm biogrenci-copy-btn" data-code="' + data.coupon_code + '">' +
209
- '<i class="fa fa-copy"></i>' +
210
- '</button>' +
245
+ '<div class="bio-claim-result">' +
246
+ '<div class="bio-coupon-box">' +
247
+ '<code>' + data.coupon_code + '</code>' +
248
+ '<button class="bio-copy-btn" data-code="' + data.coupon_code + '" title="Kopyala"><i class="fa fa-copy"></i></button>' +
249
+ '</div>' +
211
250
  (data.redirect_url
212
- ? '<a href="' + data.redirect_url + '" target="_blank" rel="noopener" class="btn btn-sm biogrenci-go-btn">Siteye Git <i class="fa fa-external-link"></i></a>'
251
+ ? '<a href="' + data.redirect_url + '" target="_blank" rel="noopener" class="bio-go-link">Siteye Git <i class="fa fa-arrow-right"></i></a>'
213
252
  : '') +
214
253
  '</div>'
215
254
  );
216
- $('.biogrenci-copy-btn').last().on('click', function () {
217
- navigator.clipboard.writeText($(this).attr('data-code'));
218
- alerts.success('Kopyalandı!');
219
- });
220
255
  } else if (data.qr_code) {
221
- $btn.replaceWith('<div class="biogrenci-qr-result">' + data.qr_code + '</div>');
256
+ $btn.replaceWith(
257
+ '<div class="bio-claim-result bio-qr-box">' + data.qr_code + '</div>'
258
+ );
222
259
  } else if (data.redirect_url) {
223
260
  window.open(data.redirect_url, '_blank');
224
- $btn.html('<i class="fa fa-check"></i> Açıldı').addClass('claimed');
261
+ $btn.addClass('bio-cta-done').html('<i class="fa fa-check"></i> Açıldı');
225
262
  }
226
263
  })
227
- .catch(function (err) {
228
- console.error('[sso-biogrenci] Claim error:', err);
264
+ .catch(function () {
229
265
  alerts.error('Bir hata oluştu');
230
- $btn.prop('disabled', false).html('<i class="fa fa-hand-pointer-o"></i> Fırsatı Al');
266
+ $btn.prop('disabled', false).text(originalText);
231
267
  });
232
268
  }
233
269