privateuserh commited on
Commit
3d94bef
·
verified ·
1 Parent(s): a985b50

Update script.js

Browse files
Files changed (1) hide show
  1. script.js +463 -0
script.js CHANGED
@@ -0,0 +1,463 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // script.js
2
+
3
+ // Set the base URL for your deployed Cloudflare Worker API
4
+ // *** IMPORTANT: REPLACE THIS WITH THE ACTUAL URL OF YOUR DEPLOYED CLOUDFLARE WORKER ***
5
+ const CLOUDFLARE_WORKER_API_BASE_URL = 'https://dmim-worker-backend.<your-subdomain>.workers.dev';
6
+
7
+ // Global state, will be populated by fetch calls
8
+ let allTrends = [];
9
+ let userData = {
10
+ dmimBalance: 0,
11
+ savedTrends: []
12
+ };
13
+ let currentSentimentTrend = null;
14
+
15
+ // Static metadata for categories (icons, colors) - these are frontend-only display properties
16
+ const performanceCategoriesMeta = {
17
+ music: { name: "Music", icon: '<i class="fas fa-music text-purple-500"></i>', color: 'bg-purple-500' },
18
+ theater: { name: "Theater", icon: '<i class="fas fa-theater-masks text-yellow-500"></i>', color: 'bg-yellow-500' },
19
+ dance: { name: "Dance", icon: '<i class="fas fa-child text-pink-500"></i>', color: 'bg-pink-500' },
20
+ comedy: { name: "Comedy", icon: '<i class="fas fa-laugh-squint text-blue-500"></i>', color: 'bg-blue-500' },
21
+ emerging: { name: "Emerging", icon: '<i class="fas fa-star text-green-500"></i>', color: 'bg-green-500' }
22
+ };
23
+
24
+ // Helper to get category metadata
25
+ function getCategoryMeta(categoryKey) {
26
+ return performanceCategoriesMeta[categoryKey] || { name: categoryKey, icon: '<i class="fas fa-hashtag"></i>', color: 'bg-gray-500' };
27
+ }
28
+
29
+ // Function to calculate market share percentage with sentiment adjustment
30
+ function calculateMarketShare(trend) {
31
+ const totalAllSearches = allTrends.reduce((sum, t) => sum + t.current_searches, 0);
32
+
33
+ const globalPercentage = totalAllSearches > 0 ? (trend.current_searches / totalAllSearches) * 100 : 0;
34
+
35
+ const rawChange = trend.previous_searches > 0
36
+ ? ((trend.current_searches - trend.previous_searches) / trend.previous_searches) * 100
37
+ : trend.current_searches > 0 ? 100 : 0;
38
+
39
+ const sentimentToUse = trend.sentiment || 0;
40
+ const sentimentMultiplier = 1 + (sentimentToUse / 200);
41
+ const adjustedChange = rawChange * sentimentMultiplier;
42
+
43
+ return {
44
+ percentage: adjustedChange > 0 ? globalPercentage.toFixed(2) : (globalPercentage * sentimentMultiplier).toFixed(2), // Apply sentiment to displayed percentage too
45
+ change: adjustedChange.toFixed(2),
46
+ rawChange: rawChange.toFixed(2),
47
+ category: trend.category,
48
+ sentiment: sentimentToUse,
49
+ sentimentHeadline: trend.sentiment_headline || ""
50
+ };
51
+ }
52
+
53
+ // Helper functions for CSS classes and labels
54
+ function getPercentageClass(change) {
55
+ const numChange = parseFloat(change);
56
+ if (numChange > 0) return 'percentage-up';
57
+ if (numChange < 0) return 'percentage-down';
58
+ return 'percentage-neutral';
59
+ }
60
+
61
+ function getSentimentClass(sentiment) {
62
+ const numSentiment = parseInt(sentiment);
63
+ if (numSentiment > 20) return 'sentiment-positive';
64
+ if (numSentiment < -20) return 'sentiment-negative';
65
+ return 'sentiment-neutral';
66
+ }
67
+
68
+ function getSentimentLabel(sentiment) {
69
+ const numSentiment = parseInt(sentiment);
70
+ if (numSentiment > 20) return 'Positive';
71
+ if (numSentiment < -20) return 'Negative';
72
+ return 'Neutral';
73
+ }
74
+
75
+ function getPlatformIcon(platform) {
76
+ return getCategoryMeta(platform).icon;
77
+ }
78
+
79
+ // Function to render trending cards
80
+ function renderTrendingCards() {
81
+ const container = document.getElementById('trendingCardsContainer');
82
+ container.innerHTML = '';
83
+
84
+ const topTrends = [...allTrends].sort((a, b) => b.current_searches - a.current_searches).slice(0, 8);
85
+
86
+ topTrends.forEach(item => {
87
+ const marketShare = calculateMarketShare(item);
88
+ const percentageClass = getPercentageClass(marketShare.change);
89
+ const sentimentClass = getSentimentClass(item.sentiment);
90
+ const categoryMeta = getCategoryMeta(item.category);
91
+
92
+ const card = document.createElement('div');
93
+ card.className = `trend-card bg-white rounded-lg shadow-sm p-4 h-40 flex flex-col justify-between transition cursor-pointer ${sentimentClass}`;
94
+ card.innerHTML = `
95
+ <div class="flex items-start justify-between mb-2">
96
+ <div>
97
+ <span class="inline-flex items-center bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">
98
+ ${getPlatformIcon(item.category)}
99
+ <span class="ml-1">${item.hashtag}</span>
100
+ </span>
101
+ </div>
102
+ <span class="inline-flex items-center bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">
103
+ <i class="fas fa-chart-line ${percentageClass} mr-1"></i>
104
+ ${marketShare.percentage}%
105
+ <span class="ml-1 ${percentageClass}">(${marketShare.change > 0 ? '+' : ''}${marketShare.change}%)</span>
106
+ </span>
107
+ </div>
108
+ <div class="flex justify-between items-center mb-3">
109
+ <span class="text-xs text-gray-500">${item.current_searches.toLocaleString()} searches</span>
110
+ <span class="text-xs px-2 py-1 rounded-full ${categoryMeta.color} text-white">
111
+ ${categoryMeta.name}
112
+ </span>
113
+ </div>
114
+ <div class="flex justify-between items-center text-xs text-gray-500">
115
+ <span>
116
+ <span class="${getSentimentClass(item.sentiment).replace('sentiment-', 'text-')}">
117
+ <i class="fas ${item.sentiment > 20 ? 'fa-smile' : item.sentiment < -20 ? 'fa-frown' : 'fa-meh'}"></i>
118
+ ${getSentimentLabel(item.sentiment)}
119
+ </span>
120
+ </span>
121
+ <button class="save-trend-btn text-gray-400 hover:text-dmim-bg" data-hashtag="${item.hashtag}">
122
+ <i class="fas fa-bookmark"></i> Save
123
+ </button>
124
+ </div>
125
+ `;
126
+ container.appendChild(card);
127
+
128
+ card.addEventListener('click', function() {
129
+ openSentimentModal(item.hashtag);
130
+ });
131
+ });
132
+
133
+ document.querySelectorAll('.save-trend-btn').forEach(btn => {
134
+ btn.addEventListener('click', function(e) {
135
+ e.stopPropagation();
136
+ const hashtag = this.getAttribute('data-hashtag');
137
+ saveTrend(hashtag);
138
+ });
139
+ });
140
+ }
141
+
142
+ // Function to render saved trends
143
+ function renderSavedTrends() {
144
+ const container = document.getElementById('savedTrendsContainer');
145
+ container.innerHTML = '';
146
+
147
+ const trendSelect = document.getElementById('trendSelect');
148
+ trendSelect.innerHTML = '<option value="">-- Select a saved trend --</option>';
149
+
150
+ userData.savedTrends.forEach(trend => {
151
+ const marketShare = calculateMarketShare(trend);
152
+ const sentimentClass = getSentimentClass(trend.user_sentiment !== null ? trend.user_sentiment : trend.sentiment);
153
+ const categoryMeta = getCategoryMeta(trend.category);
154
+
155
+ const element = document.createElement('div');
156
+ element.className = `flex items-center p-3 rounded-lg bg-white shadow-sm cursor-pointer hover:bg-gray-50 ${sentimentClass}`;
157
+ element.innerHTML = `
158
+ <div class="flex-shrink-0 mr-3">
159
+ <div class="w-8 h-8 rounded-full flex items-center justify-center text-white ${categoryMeta.color}">
160
+ ${getPlatformIcon(trend.category)}
161
+ </div>
162
+ </div>
163
+ <div class="flex-1">
164
+ <div class="flex items-center">
165
+ <h4 class="font-medium text-sm">${trend.hashtag}</h4>
166
+ ${trend.staked_amount > 0 ? '<span class="ml-1 text-green-500"><i class="fas fa-check-circle"></i></span>' : ''}
167
+ </div>
168
+ <div class="flex items-center">
169
+ <p class="text-gray-500 text-xs mr-2">${categoryMeta.name}</p>
170
+ <span class="text-xs ${percentageClass}">
171
+ ${marketShare.percentage}% (${marketShare.change > 0 ? '+' : ''}${marketShare.change}%)
172
+ </span>
173
+ </div>
174
+ </div>
175
+ <button class="text-gray-500 hover:text-dmim-bg sentiment-btn" data-hashtag="${trend.hashtag}">
176
+ <i class="fas fa-ellipsis-v"></i>
177
+ </button>
178
+ `;
179
+ container.appendChild(element);
180
+
181
+ const option = document.createElement('option');
182
+ option.value = trend.hashtag;
183
+ option.textContent = trend.hashtag;
184
+ trendSelect.appendChild(option);
185
+
186
+ element.addEventListener('click', function() {
187
+ openSentimentModal(trend.hashtag);
188
+ });
189
+ });
190
+
191
+ document.getElementById('dmimBalance').textContent = userData.dmimBalance + ' DMIM';
192
+ document.getElementById('dmimBalanceDisplay').textContent = userData.dmimBalance + ' DMIM';
193
+ }
194
+
195
+ // Function to open sentiment modal
196
+ async function openSentimentModal(hashtag) {
197
+ currentSentimentTrend = hashtag;
198
+
199
+ const trendData = allTrends.find(t => t.hashtag === hashtag);
200
+
201
+ if (!trendData) {
202
+ showToast('Trend data not found for sentiment adjustment.');
203
+ return;
204
+ }
205
+
206
+ document.getElementById('sentimentTrendName').textContent = hashtag;
207
+ document.getElementById('sentimentSlider').value = trendData.user_sentiment !== null ? trendData.user_sentiment : trendData.sentiment;
208
+ document.getElementById('sentimentHeadline').value = trendData.user_sentiment_headline || trendData.sentiment_headline || "";
209
+
210
+ updateSentimentSlider(document.getElementById('sentimentSlider').value);
211
+
212
+ document.getElementById('sentimentModal').classList.remove('hidden');
213
+ }
214
+
215
+ // Function to update sentiment slider appearance
216
+ function updateSentimentSlider(value) {
217
+ const slider = document.getElementById('sentimentSlider');
218
+ slider.value = value;
219
+ slider.classList.remove('positive', 'negative', 'neutral');
220
+ if (value > 20) {
221
+ slider.classList.add('positive');
222
+ } else if (value < -20) {
223
+ slider.classList.add('negative');
224
+ } else {
225
+ slider.classList.add('neutral');
226
+ }
227
+
228
+ const impactText = document.getElementById('sentimentImpactText');
229
+ if (value > 20) {
230
+ impactText.textContent = `Positive sentiment will boost growth by ${Math.round(value/2)}%.`;
231
+ impactText.className = "text-sm text-green-600";
232
+ } else if (value < -20) {
233
+ impactText.textContent = `Negative sentiment will reduce growth by ${Math.round(Math.abs(value)/2)}%.`;
234
+ impactText.className = "text-sm text-red-600";
235
+ } else {
236
+ impactText.textContent = "Neutral sentiment will not affect trend growth.";
237
+ impactText.className = "text-sm text-gray-600";
238
+ }
239
+ }
240
+
241
+ // Function to show search results
242
+ async function showSearchResults(query) {
243
+ const container = document.getElementById('searchResultsContainer');
244
+ container.innerHTML = '<p class="text-center text-gray-500 mt-8">Searching...</p>';
245
+
246
+ if (!query) {
247
+ container.innerHTML = '';
248
+ return;
249
+ }
250
+
251
+ try {
252
+ const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/search?query=${encodeURIComponent(query)}`);
253
+ if (!response.ok) {
254
+ const errorData = await response.json();
255
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
256
+ }
257
+ const results = await response.json();
258
+
259
+ container.innerHTML = '';
260
+ if (results.length === 0) {
261
+ container.innerHTML = '<p class="text-center text-gray-500 mt-8">No results found. Consider adding it as a new trend!</p>';
262
+ showToast(`No results found for ${query}`);
263
+ return;
264
+ }
265
+
266
+ results.forEach(item => {
267
+ const marketShare = calculateMarketShare(item);
268
+ const percentageClass = getPercentageClass(marketShare.change);
269
+ const sentimentClass = getSentimentClass(item.sentiment);
270
+ const categoryMeta = getCategoryMeta(item.category);
271
+
272
+ const card = document.createElement('div');
273
+ card.className = `trend-card bg-white rounded-lg shadow-sm p-4 h-40 flex flex-col justify-between transition cursor-pointer ${sentimentClass}`;
274
+ card.innerHTML = `
275
+ <div class="flex items-start justify-between mb-2">
276
+ <div>
277
+ <span class="inline-flex items-center bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">
278
+ ${getPlatformIcon(item.category)}
279
+ <span class="ml-1">${item.hashtag}</span>
280
+ </span>
281
+ </div>
282
+ <span class="inline-flex items-center bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">
283
+ <i class="fas fa-chart-line ${percentageClass} mr-1"></i>
284
+ ${marketShare.percentage}%
285
+ <span class="ml-1 ${percentageClass}">(${marketShare.change > 0 ? '+' : ''}${marketShare.change}%)</span>
286
+ </span>
287
+ </div>
288
+ <div class="flex justify-between items-center mb-3">
289
+ <span class="text-xs text-gray-500">${item.current_searches.toLocaleString()} searches</span>
290
+ <span class="text-xs px-2 py-1 rounded-full ${categoryMeta.color} text-white">
291
+ ${categoryMeta.name}
292
+ </span>
293
+ </div>
294
+ <div class="flex justify-between items-center text-xs text-gray-500">
295
+ <span>
296
+ <span class="${getSentimentClass(item.sentiment).replace('sentiment-', 'text-')}">
297
+ <i class="fas ${item.sentiment > 20 ? 'fa-smile' : item.sentiment < -20 ? 'fa-frown' : 'fa-meh'}"></i>
298
+ ${getSentimentLabel(item.sentiment)}
299
+ </span>
300
+ </span>
301
+ <button class="save-trend-btn text-gray-400 hover:text-dmim-bg" data-hashtag="${item.hashtag}">
302
+ <i class="fas fa-bookmark"></i> Save
303
+ </button>
304
+ </div>
305
+ `;
306
+ container.appendChild(card);
307
+
308
+ card.addEventListener('click', function() {
309
+ openSentimentModal(item.hashtag);
310
+ });
311
+ });
312
+
313
+ document.querySelectorAll('.save-trend-btn').forEach(btn => {
314
+ btn.addEventListener('click', function(e) {
315
+ e.stopPropagation();
316
+ const hashtag = this.getAttribute('data-hashtag');
317
+ saveTrend(hashtag);
318
+ });
319
+ });
320
+
321
+ showToast(`Found ${results.length} results for ${query}`);
322
+
323
+ } catch (error) {
324
+ console.error("Error searching trends:", error);
325
+ showToast(`Error searching for ${query}: ${error.message}`);
326
+ container.innerHTML = '<p class="text-center text-red-500 mt-8">Failed to fetch search results.</p>';
327
+ }
328
+ }
329
+
330
+ // Function to save a trend (calls backend API)
331
+ async function saveTrend(hashtag) {
332
+ if (userData.savedTrends.some(t => t.hashtag === hashtag)) {
333
+ showToast('This trend is already saved');
334
+ return;
335
+ }
336
+
337
+ try {
338
+ const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends/${encodeURIComponent(hashtag)}/save`, {
339
+ method: 'PUT',
340
+ headers: { 'Content-Type': 'application/json' },
341
+ });
342
+ if (!response.ok) {
343
+ const errorData = await response.json();
344
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
345
+ }
346
+
347
+ await initApp();
348
+ showToast(`${hashtag} saved to your library`);
349
+ } catch (error) {
350
+ console.error("Error saving trend:", error);
351
+ showToast(`Error saving ${hashtag}: ${error.message}`);
352
+ }
353
+ }
354
+
355
+ // Function to stake DMIM to a trend (calls backend API)
356
+ async function stakeDmim(hashtag, amount) {
357
+ if (!hashtag || !amount || amount <= 0) {
358
+ showToast('Please select a trend and enter a valid amount');
359
+ return;
360
+ }
361
+
362
+ if (amount > userData.dmimBalance) {
363
+ showToast('Insufficient DMIM balance');
364
+ return;
365
+ }
366
+
367
+ try {
368
+ const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends/${encodeURIComponent(hashtag)}/stake`, {
369
+ method: 'PUT',
370
+ headers: { 'Content-Type': 'application/json' },
371
+ body: JSON.stringify({ amount: amount })
372
+ });
373
+ if (!response.ok) {
374
+ const errorData = await response.json();
375
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
376
+ }
377
+
378
+ userData.dmimBalance -= amount; // Optimistic update
379
+ await initApp();
380
+ showToast(`Staked ${amount} DMIM to ${hashtag}`);
381
+ } catch (error) {
382
+ console.error("Error staking DMIM:", error);
383
+ showToast(`Error staking DMIM to ${hashtag}: ${error.message}`);
384
+ }
385
+ }
386
+
387
+ // Function to add DMIM tokens (client-side simulation for demo)
388
+ async function addDmim(amount) {
389
+ userData.dmimBalance += amount;
390
+ renderSavedTrends();
391
+ showToast(`Added ${amount} DMIM to your balance`);
392
+ }
393
+
394
+ // Function to save sentiment for a trend (calls backend API)
395
+ async function saveSentiment(hashtag, sentiment, headline) {
396
+ try {
397
+ const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends/${encodeURIComponent(hashtag)}/sentiment`, {
398
+ method: 'PUT',
399
+ headers: { 'Content-Type': 'application/json' },
400
+ body: JSON.stringify({ sentiment: sentiment, headline: headline })
401
+ });
402
+ if (!response.ok) {
403
+ const errorData = await response.json();
404
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
405
+ }
406
+
407
+ await initApp();
408
+ showToast(`Sentiment updated for ${hashtag}`);
409
+ } catch (error) {
410
+ console.error("Error saving sentiment:", error);
411
+ showToast(`Error updating sentiment for ${hashtag}: ${error.message}`);
412
+ } finally {
413
+ document.getElementById('sentimentModal').classList.add('hidden');
414
+ }
415
+ }
416
+
417
+ // Initialize the app - fetches all data from backend
418
+ async function initApp() {
419
+ try {
420
+ // Fetch all trends
421
+ const trendsResponse = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends`);
422
+ if (!trendsResponse.ok) {
423
+ const errorData = await trendsResponse.json();
424
+ throw new Error(errorData.error || `Failed to fetch trends with status: ${trendsResponse.status}`);
425
+ }
426
+ allTrends = await trendsResponse.json();
427
+
428
+ // Filter saved trends for the local userData object
429
+ userData.savedTrends = allTrends.filter(t => t.is_saved_by_user);
430
+
431
+ // Fetch DMIM balance
432
+ const dmimResponse = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/dmim_balance`);
433
+ if (dmimResponse.ok) {
434
+ const dmimData = await dmimResponse.json();
435
+ userData.dmimBalance = dmimData.balance;
436
+ } else {
437
+ console.warn("Could not fetch DMIM balance, using default for demo.");
438
+ userData.dmimBalance = 1000;
439
+ }
440
+
441
+ renderTrendingCards();
442
+ renderSavedTrends();
443
+
444
+ } catch (error) {
445
+ console.error("Failed to initialize app from backend:", error);
446
+ showToast(`Failed to load data: ${error.message}. Please try again.`);
447
+ }
448
+ }
449
+
450
+ // --- Event Listeners ---
451
+
452
+ // Tab switching functionality
453
+ document.querySelectorAll('.tab-button').forEach(button => {
454
+ button.addEventListener('click', function() {
455
+ document.querySelectorAll('.tab-button').forEach(btn => {
456
+ btn.classList.remove('active', 'text-dmim-bg');
457
+ btn.classList.add('text-gray-500');
458
+ });
459
+ this.classList.add('active', 'text-dmim-bg');
460
+ this.classList.remove('text-gray-500');
461
+ document.querySelectorAll('#mainContent > div').forEach(tab => {
462
+ tab.classList.add('hidden');
463
+