Spaces:
Running
Running
Update script.js
Browse files
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 |
+
|