@kaikybrofc/omnizap-system 2.2.5 → 2.2.6
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/.prettierrc +16 -0
- package/README.md +10 -10
- package/app/modules/stickerPackModule/autoPackCollectorService.js +63 -8
- package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +33 -7
- package/kaikybrofc-omnizap-system-2.2.6.tgz +0 -0
- package/observability/sticker-catalog-slo.md +83 -0
- package/observability/sticker-scale-hardening-rollout.md +128 -0
- package/package.json +3 -35
- package/public/assets/images/brand-icon-192.png +0 -0
- package/public/assets/images/brand-logo-128.webp +0 -0
- package/public/assets/images/hero-banner-1280.avif +0 -0
- package/public/assets/images/hero-banner-1280.jpg +0 -0
- package/public/assets/images/hero-banner-1280.webp +0 -0
- package/public/assets/images/hero-banner-720.avif +0 -0
- package/public/assets/images/hero-banner-720.webp +0 -0
- package/public/index.html +91 -16
- package/public/js/apps/homeApp.js +469 -353
- package/public/robots.txt +9 -0
- package/public/sitemap.xml +28 -0
- package/observability/mysql-exporter.cnf +0 -5
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
const FALLBACK_THUMB_URL = '/assets/images/brand-logo-128.webp';
|
|
2
2
|
|
|
3
|
-
const h = React.createElement;
|
|
4
3
|
const shortNum = (value) =>
|
|
5
4
|
new Intl.NumberFormat('pt-BR', {
|
|
6
5
|
notation: Number(value) >= 1000 ? 'compact' : 'standard',
|
|
@@ -39,286 +38,360 @@ const animateCountUp = (element, value, formatter = shortNum, durationMs = 850)
|
|
|
39
38
|
requestAnimationFrame(tick);
|
|
40
39
|
};
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const proofUsers = document.getElementById('proof-users');
|
|
48
|
-
const proofGroups = document.getElementById('proof-groups');
|
|
49
|
-
const proofSystem = document.getElementById('proof-system');
|
|
50
|
-
const previewStatus = document.getElementById('hero-preview-status');
|
|
51
|
-
const previewGrid = document.getElementById('hero-pack-preview');
|
|
52
|
-
if (
|
|
53
|
-
!proofPacks
|
|
54
|
-
|| !proofStickers
|
|
55
|
-
|| !proofDownloads
|
|
56
|
-
|| !proofUsers
|
|
57
|
-
|| !proofGroups
|
|
58
|
-
|| !proofSystem
|
|
59
|
-
|| !previewStatus
|
|
60
|
-
|| !previewGrid
|
|
61
|
-
) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
41
|
+
const runAfterLoadIdle = (callback, { delayMs = 0, timeoutMs = 2200 } = {}) => {
|
|
42
|
+
let cancelled = false;
|
|
43
|
+
let loadListener = null;
|
|
44
|
+
let timeoutId = null;
|
|
45
|
+
let idleId = null;
|
|
64
46
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const renderPreviewSkeleton = (count = 6) => {
|
|
70
|
-
previewGrid.innerHTML = Array.from({ length: count })
|
|
71
|
-
.map(
|
|
72
|
-
() =>
|
|
73
|
-
'<article class="market-pack is-loading">' +
|
|
74
|
-
'<div class="market-pack-skeleton-thumb"></div>' +
|
|
75
|
-
'<div class="market-pack-skeleton-body">' +
|
|
76
|
-
'<span class="market-pack-skeleton-line"></span>' +
|
|
77
|
-
'<span class="market-pack-skeleton-line short"></span>' +
|
|
78
|
-
'</div>' +
|
|
79
|
-
'</article>',
|
|
80
|
-
)
|
|
81
|
-
.join('');
|
|
82
|
-
};
|
|
47
|
+
const invoke = () => {
|
|
48
|
+
if (cancelled) return;
|
|
49
|
+
callback();
|
|
50
|
+
};
|
|
83
51
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
52
|
+
const schedule = () => {
|
|
53
|
+
if (cancelled) return;
|
|
54
|
+
|
|
55
|
+
const queuedInvoke = () => {
|
|
56
|
+
if (cancelled) return;
|
|
57
|
+
if (delayMs > 0) {
|
|
58
|
+
timeoutId = window.setTimeout(invoke, delayMs);
|
|
88
59
|
return;
|
|
89
60
|
}
|
|
90
|
-
|
|
91
|
-
previewStatus.textContent = `${packs.length} packs sugeridos agora`;
|
|
92
|
-
packs.slice(0, 6).forEach((pack, index) => {
|
|
93
|
-
const card = document.createElement('a');
|
|
94
|
-
card.className = 'market-pack reveal';
|
|
95
|
-
card.href = pack.web_url || `/stickers/${encodeURIComponent(pack.pack_key || '')}`;
|
|
96
|
-
card.innerHTML =
|
|
97
|
-
`<img class="market-pack-thumb" loading="lazy" src="${pack.cover_url || fallbackThumb}" alt="${String(
|
|
98
|
-
pack.name || 'Pack',
|
|
99
|
-
).replace(/"/g, '"')}">` +
|
|
100
|
-
(isAutoPack(pack) ? '<span class="market-pack-tag">auto</span>' : '') +
|
|
101
|
-
'<div class="market-pack-body">' +
|
|
102
|
-
`<p class="market-pack-name">${pack.name || 'Pack sem nome'}</p>` +
|
|
103
|
-
`<p class="market-pack-meta">${shortNum(pack.sticker_count || 0)} stickers · ${shortNum(
|
|
104
|
-
pack?.engagement?.open_count || 0,
|
|
105
|
-
)} aberturas</p>` +
|
|
106
|
-
'</div>';
|
|
107
|
-
card.style.transitionDelay = `${index * 40}ms`;
|
|
108
|
-
previewGrid.appendChild(card);
|
|
109
|
-
requestAnimationFrame(() => card.classList.add('in-view'));
|
|
110
|
-
});
|
|
61
|
+
invoke();
|
|
111
62
|
};
|
|
112
63
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
fetch('/api/sticker-packs/intents?limit=12'),
|
|
118
|
-
]);
|
|
119
|
-
|
|
120
|
-
const statsPayload = statsResponse.ok ? await statsResponse.json() : null;
|
|
121
|
-
const intentsPayload = intentsResponse.ok ? await intentsResponse.json() : null;
|
|
122
|
-
const stats = statsPayload?.data || {};
|
|
123
|
-
const intents = intentsPayload?.data || {};
|
|
124
|
-
const trending = Array.isArray(intents?.em_alta) ? intents.em_alta : [];
|
|
125
|
-
|
|
126
|
-
animateCountUp(proofPacks, stats.packs_total || 0);
|
|
127
|
-
animateCountUp(proofStickers, stats.stickers_total || 0);
|
|
128
|
-
animateCountUp(proofDownloads, stats.downloads_total || 0);
|
|
129
|
-
renderPreview(trending);
|
|
130
|
-
} catch {
|
|
131
|
-
proofPacks.textContent = 'n/d';
|
|
132
|
-
proofStickers.textContent = 'n/d';
|
|
133
|
-
proofDownloads.textContent = 'n/d';
|
|
134
|
-
proofUsers.textContent = 'n/d';
|
|
135
|
-
proofGroups.textContent = 'n/d';
|
|
136
|
-
proofSystem.textContent = 'n/d';
|
|
137
|
-
previewGrid.innerHTML = '';
|
|
138
|
-
previewStatus.textContent = 'Não foi possível carregar o preview agora.';
|
|
139
|
-
}
|
|
140
|
-
};
|
|
64
|
+
if (typeof window.requestIdleCallback === 'function') {
|
|
65
|
+
idleId = window.requestIdleCallback(queuedInvoke, { timeout: timeoutMs });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
141
68
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}, []);
|
|
69
|
+
timeoutId = window.setTimeout(queuedInvoke, Math.min(350, delayMs || 120));
|
|
70
|
+
};
|
|
145
71
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
72
|
+
if (document.readyState === 'complete') {
|
|
73
|
+
schedule();
|
|
74
|
+
} else {
|
|
75
|
+
loadListener = () => schedule();
|
|
76
|
+
window.addEventListener('load', loadListener, { once: true });
|
|
77
|
+
}
|
|
150
78
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true;
|
|
81
|
+
if (loadListener) {
|
|
82
|
+
window.removeEventListener('load', loadListener);
|
|
83
|
+
}
|
|
84
|
+
if (timeoutId !== null) {
|
|
85
|
+
window.clearTimeout(timeoutId);
|
|
86
|
+
}
|
|
87
|
+
if (idleId !== null && typeof window.cancelIdleCallback === 'function') {
|
|
88
|
+
window.cancelIdleCallback(idleId);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
156
92
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
93
|
+
const initMarketplacePreview = () => {
|
|
94
|
+
const proofPacks = document.getElementById('proof-packs');
|
|
95
|
+
const proofStickers = document.getElementById('proof-stickers');
|
|
96
|
+
const proofDownloads = document.getElementById('proof-downloads');
|
|
97
|
+
const proofUsers = document.getElementById('proof-users');
|
|
98
|
+
const proofGroups = document.getElementById('proof-groups');
|
|
99
|
+
const proofSystem = document.getElementById('proof-system');
|
|
100
|
+
const previewStatus = document.getElementById('hero-preview-status');
|
|
101
|
+
const previewGrid = document.getElementById('hero-pack-preview');
|
|
102
|
+
|
|
103
|
+
if (
|
|
104
|
+
!proofPacks
|
|
105
|
+
|| !proofStickers
|
|
106
|
+
|| !proofDownloads
|
|
107
|
+
|| !proofUsers
|
|
108
|
+
|| !proofGroups
|
|
109
|
+
|| !proofSystem
|
|
110
|
+
|| !previewStatus
|
|
111
|
+
|| !previewGrid
|
|
112
|
+
) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
161
115
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
116
|
+
const isAutoPack = (pack) =>
|
|
117
|
+
Number(pack?.is_auto_pack || pack?.auto_pack || 0) === 1 || /\[auto\]/i.test(String(pack?.name || ''));
|
|
118
|
+
|
|
119
|
+
const renderPreviewSkeleton = (count = 6) => {
|
|
120
|
+
previewGrid.innerHTML = Array.from({ length: count })
|
|
121
|
+
.map(
|
|
122
|
+
() =>
|
|
123
|
+
'<article class="market-pack is-loading">' +
|
|
124
|
+
'<div class="market-pack-skeleton-thumb"></div>' +
|
|
125
|
+
'<div class="market-pack-skeleton-body">' +
|
|
126
|
+
'<span class="market-pack-skeleton-line"></span>' +
|
|
127
|
+
'<span class="market-pack-skeleton-line short"></span>' +
|
|
128
|
+
'</div>' +
|
|
129
|
+
'</article>',
|
|
130
|
+
)
|
|
131
|
+
.join('');
|
|
132
|
+
};
|
|
178
133
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const avatar = document.createElement('img');
|
|
186
|
-
avatar.className = 'rank-avatar';
|
|
187
|
-
avatar.alt = row.display_name || 'Usuário';
|
|
188
|
-
avatar.loading = 'lazy';
|
|
189
|
-
avatar.src = row.avatar_url || 'https://iili.io/FC3FABe.jpg';
|
|
190
|
-
const name = document.createElement('span');
|
|
191
|
-
name.className = 'rank-name';
|
|
192
|
-
name.textContent = `${row.position}. ${row.display_name || 'Desconhecido'}`;
|
|
193
|
-
userWrap.append(avatar, name);
|
|
194
|
-
const value = document.createElement('span');
|
|
195
|
-
value.className = 'rank-value';
|
|
196
|
-
value.textContent = `${Number(row.total_messages || 0)} msg · ${Number(row.percent_of_total || 0).toFixed(2)}%`;
|
|
197
|
-
item.append(userWrap, value);
|
|
198
|
-
listEl.appendChild(item);
|
|
199
|
-
});
|
|
200
|
-
};
|
|
134
|
+
const renderPreview = (packs) => {
|
|
135
|
+
previewGrid.innerHTML = '';
|
|
136
|
+
if (!Array.isArray(packs) || !packs.length) {
|
|
137
|
+
previewStatus.textContent = 'Sem packs em destaque no momento.';
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
201
140
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
.
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (!toggle || !nav) return;
|
|
224
|
-
|
|
225
|
-
const handleClick = () => {
|
|
226
|
-
const isOpen = nav.classList.toggle('open');
|
|
227
|
-
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
|
228
|
-
};
|
|
141
|
+
previewStatus.textContent = `${packs.length} packs sugeridos agora`;
|
|
142
|
+
packs.slice(0, 6).forEach((pack, index) => {
|
|
143
|
+
const card = document.createElement('a');
|
|
144
|
+
card.className = 'market-pack reveal';
|
|
145
|
+
card.href = pack.web_url || `/stickers/${encodeURIComponent(pack.pack_key || '')}`;
|
|
146
|
+
card.innerHTML =
|
|
147
|
+
`<img class="market-pack-thumb" loading="lazy" decoding="async" fetchpriority="low" width="320" height="320" src="${pack.cover_url || FALLBACK_THUMB_URL}" alt="${String(
|
|
148
|
+
pack.name || 'Pack',
|
|
149
|
+
).replace(/"/g, '"')}">` +
|
|
150
|
+
(isAutoPack(pack) ? '<span class="market-pack-tag">auto</span>' : '') +
|
|
151
|
+
'<div class="market-pack-body">' +
|
|
152
|
+
`<p class="market-pack-name">${pack.name || 'Pack sem nome'}</p>` +
|
|
153
|
+
`<p class="market-pack-meta">${shortNum(pack.sticker_count || 0)} stickers · ${shortNum(
|
|
154
|
+
pack?.engagement?.open_count || 0,
|
|
155
|
+
)} aberturas</p>` +
|
|
156
|
+
'</div>';
|
|
157
|
+
card.style.transitionDelay = `${index * 40}ms`;
|
|
158
|
+
previewGrid.appendChild(card);
|
|
159
|
+
requestAnimationFrame(() => card.classList.add('in-view'));
|
|
160
|
+
});
|
|
161
|
+
};
|
|
229
162
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
163
|
+
const loadMarketplaceData = async () => {
|
|
164
|
+
try {
|
|
165
|
+
const [statsResponse, intentsResponse] = await Promise.all([
|
|
166
|
+
fetch('/api/sticker-packs/stats'),
|
|
167
|
+
fetch('/api/sticker-packs/intents?limit=12'),
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
const statsPayload = statsResponse.ok ? await statsResponse.json() : null;
|
|
171
|
+
const intentsPayload = intentsResponse.ok ? await intentsResponse.json() : null;
|
|
172
|
+
const stats = statsPayload?.data || {};
|
|
173
|
+
const intents = intentsPayload?.data || {};
|
|
174
|
+
const trending = Array.isArray(intents?.em_alta) ? intents.em_alta : [];
|
|
175
|
+
|
|
176
|
+
animateCountUp(proofPacks, stats.packs_total || 0);
|
|
177
|
+
animateCountUp(proofStickers, stats.stickers_total || 0);
|
|
178
|
+
animateCountUp(proofDownloads, stats.downloads_total || 0);
|
|
179
|
+
renderPreview(trending);
|
|
180
|
+
} catch {
|
|
181
|
+
proofPacks.textContent = 'n/d';
|
|
182
|
+
proofStickers.textContent = 'n/d';
|
|
183
|
+
proofDownloads.textContent = 'n/d';
|
|
184
|
+
proofUsers.textContent = 'n/d';
|
|
185
|
+
proofGroups.textContent = 'n/d';
|
|
186
|
+
proofSystem.textContent = 'n/d';
|
|
187
|
+
previewGrid.innerHTML = '';
|
|
188
|
+
previewStatus.textContent = 'Não foi possível carregar o preview agora.';
|
|
189
|
+
}
|
|
190
|
+
};
|
|
233
191
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
192
|
+
renderPreviewSkeleton(6);
|
|
193
|
+
return runAfterLoadIdle(() => {
|
|
194
|
+
void loadMarketplaceData();
|
|
195
|
+
}, { timeoutMs: 1200 });
|
|
196
|
+
};
|
|
239
197
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
};
|
|
198
|
+
const initRankingPanel = () => {
|
|
199
|
+
const summaryEl = document.getElementById('rank-summary');
|
|
200
|
+
const listEl = document.getElementById('rank-list');
|
|
201
|
+
if (!summaryEl || !listEl) return null;
|
|
246
202
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
authLink.removeAttribute('aria-label');
|
|
253
|
-
clearChildren(authLink);
|
|
254
|
-
|
|
255
|
-
if (schedulerLink) {
|
|
256
|
-
schedulerLink.hidden = false;
|
|
257
|
-
schedulerLink.removeAttribute('aria-hidden');
|
|
258
|
-
}
|
|
203
|
+
const formatDate = (value) => {
|
|
204
|
+
const time = Date.parse(String(value || ''));
|
|
205
|
+
if (!Number.isFinite(time)) return 'n/d';
|
|
206
|
+
return new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(time));
|
|
207
|
+
};
|
|
259
208
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
209
|
+
const renderFallback = (message) => {
|
|
210
|
+
summaryEl.textContent = message || 'Ranking indisponível no momento.';
|
|
211
|
+
listEl.innerHTML = '<li class="rank-item"><span class="rank-name">Sem dados no momento</span><span class="rank-value">--</span></li>';
|
|
212
|
+
};
|
|
264
213
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
214
|
+
const renderRanking = (data) => {
|
|
215
|
+
const rows = Array.isArray(data?.rows) ? data.rows : [];
|
|
216
|
+
const topType = data?.top_type ? `${data.top_type} (${data.top_type_count || 0})` : 'N/D';
|
|
217
|
+
summaryEl.textContent =
|
|
218
|
+
`Top recentes: ${Number(data?.top_share_percent || 0).toFixed(
|
|
219
|
+
2,
|
|
220
|
+
)}% das ${Number(data?.total_messages || 0)} mensagens · Tipo mais usado: ${topType} · Janela: ${Number(
|
|
221
|
+
data?.sample_limit || 0,
|
|
222
|
+
)} msgs · Atualizado: ${formatDate(
|
|
223
|
+
data?.updated_at,
|
|
224
|
+
)}`;
|
|
225
|
+
|
|
226
|
+
if (!rows.length) {
|
|
227
|
+
renderFallback('Ainda não há mensagens suficientes para o ranking global.');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
270
230
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
231
|
+
listEl.innerHTML = '';
|
|
232
|
+
rows.forEach((row) => {
|
|
233
|
+
const item = document.createElement('li');
|
|
234
|
+
item.className = 'rank-item';
|
|
235
|
+
const userWrap = document.createElement('span');
|
|
236
|
+
userWrap.className = 'rank-user';
|
|
237
|
+
const avatar = document.createElement('img');
|
|
238
|
+
avatar.className = 'rank-avatar';
|
|
239
|
+
avatar.alt = row.display_name || 'Usuário';
|
|
240
|
+
avatar.loading = 'lazy';
|
|
241
|
+
avatar.decoding = 'async';
|
|
242
|
+
avatar.setAttribute('fetchpriority', 'low');
|
|
243
|
+
avatar.width = 34;
|
|
244
|
+
avatar.height = 34;
|
|
245
|
+
avatar.src = row.avatar_url || FALLBACK_THUMB_URL;
|
|
246
|
+
avatar.onerror = () => {
|
|
247
|
+
avatar.src = FALLBACK_THUMB_URL;
|
|
248
|
+
};
|
|
249
|
+
const name = document.createElement('span');
|
|
250
|
+
name.className = 'rank-name';
|
|
251
|
+
name.textContent = `${row.position}. ${row.display_name || 'Desconhecido'}`;
|
|
252
|
+
userWrap.append(avatar, name);
|
|
253
|
+
const value = document.createElement('span');
|
|
254
|
+
value.className = 'rank-value';
|
|
255
|
+
value.textContent = `${Number(row.total_messages || 0)} msg · ${Number(row.percent_of_total || 0).toFixed(2)}%`;
|
|
256
|
+
item.append(userWrap, value);
|
|
257
|
+
listEl.appendChild(item);
|
|
258
|
+
});
|
|
259
|
+
};
|
|
287
260
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
261
|
+
const loadRanking = () =>
|
|
262
|
+
fetch('/api/sticker-packs/global-ranking-summary')
|
|
263
|
+
.then((response) => {
|
|
264
|
+
if (!response.ok) throw new Error('Falha ao carregar ranking');
|
|
265
|
+
return response.json();
|
|
266
|
+
})
|
|
267
|
+
.then((payload) => {
|
|
268
|
+
renderRanking(payload?.data || {});
|
|
269
|
+
})
|
|
270
|
+
.catch(() => {
|
|
271
|
+
renderFallback('Ranking indisponível no momento.');
|
|
272
|
+
});
|
|
292
273
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
274
|
+
let intervalId = null;
|
|
275
|
+
const cancelScheduledLoad = runAfterLoadIdle(() => {
|
|
276
|
+
void loadRanking();
|
|
277
|
+
intervalId = window.setInterval(() => {
|
|
278
|
+
void loadRanking();
|
|
279
|
+
}, 10 * 60 * 1000);
|
|
280
|
+
}, { delayMs: 450, timeoutMs: 2200 });
|
|
281
|
+
|
|
282
|
+
return () => {
|
|
283
|
+
cancelScheduledLoad();
|
|
284
|
+
if (intervalId !== null) {
|
|
285
|
+
window.clearInterval(intervalId);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
};
|
|
306
289
|
|
|
307
|
-
|
|
308
|
-
|
|
290
|
+
const initNavToggle = () => {
|
|
291
|
+
const toggle = document.getElementById('nav-toggle');
|
|
292
|
+
const nav = document.getElementById('main-nav');
|
|
293
|
+
if (!toggle || !nav) return null;
|
|
309
294
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
295
|
+
const handleClick = () => {
|
|
296
|
+
const isOpen = nav.classList.toggle('open');
|
|
297
|
+
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
|
298
|
+
};
|
|
313
299
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
300
|
+
toggle.addEventListener('click', handleClick);
|
|
301
|
+
return () => toggle.removeEventListener('click', handleClick);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const initAuthSession = () => {
|
|
305
|
+
const authLink = document.getElementById('nav-auth-link');
|
|
306
|
+
const schedulerLink = document.getElementById('nav-scheduler-link');
|
|
307
|
+
const heroLoginCta = document.getElementById('hero-login-cta');
|
|
308
|
+
if (!authLink) return null;
|
|
309
|
+
|
|
310
|
+
const clearChildren = (node) => {
|
|
311
|
+
while (node.firstChild) {
|
|
312
|
+
node.removeChild(node.firstChild);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const showLoginButton = () => {
|
|
317
|
+
document.body.classList.remove('home-authenticated');
|
|
318
|
+
authLink.classList.remove('nav-user-chip');
|
|
319
|
+
authLink.href = '/login/';
|
|
320
|
+
authLink.removeAttribute('title');
|
|
321
|
+
authLink.removeAttribute('aria-label');
|
|
322
|
+
clearChildren(authLink);
|
|
323
|
+
|
|
324
|
+
if (schedulerLink) {
|
|
325
|
+
schedulerLink.hidden = false;
|
|
326
|
+
schedulerLink.removeAttribute('aria-hidden');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (heroLoginCta) {
|
|
330
|
+
heroLoginCta.hidden = false;
|
|
331
|
+
heroLoginCta.removeAttribute('aria-hidden');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const icon = document.createElement('i');
|
|
335
|
+
icon.className = 'fa-solid fa-right-to-bracket icon-inline';
|
|
336
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
337
|
+
authLink.append(icon, document.createTextNode('Login'));
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const showLoggedUser = (sessionData) => {
|
|
341
|
+
const profile = sessionData?.user || {};
|
|
342
|
+
const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
|
|
343
|
+
const resolvedPhoto = String(profile?.picture || '').trim() || FALLBACK_THUMB_URL;
|
|
344
|
+
|
|
345
|
+
document.body.classList.add('home-authenticated');
|
|
346
|
+
authLink.classList.add('nav-user-chip');
|
|
347
|
+
authLink.href = '/user/';
|
|
348
|
+
authLink.title = `${resolvedName} (sessão ativa)`;
|
|
349
|
+
authLink.setAttribute('aria-label', `Sessão ativa de ${resolvedName}`);
|
|
350
|
+
clearChildren(authLink);
|
|
351
|
+
|
|
352
|
+
if (schedulerLink) {
|
|
353
|
+
schedulerLink.hidden = true;
|
|
354
|
+
schedulerLink.setAttribute('aria-hidden', 'true');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (heroLoginCta) {
|
|
358
|
+
heroLoginCta.hidden = true;
|
|
359
|
+
heroLoginCta.setAttribute('aria-hidden', 'true');
|
|
360
|
+
}
|
|
317
361
|
|
|
318
|
-
|
|
319
|
-
|
|
362
|
+
const avatarBubble = document.createElement('span');
|
|
363
|
+
avatarBubble.className = 'nav-user-avatar-bubble';
|
|
364
|
+
|
|
365
|
+
const photo = document.createElement('img');
|
|
366
|
+
photo.className = 'nav-user-photo';
|
|
367
|
+
photo.src = resolvedPhoto;
|
|
368
|
+
photo.alt = `Foto de ${resolvedName}`;
|
|
369
|
+
photo.loading = 'lazy';
|
|
370
|
+
photo.decoding = 'async';
|
|
371
|
+
photo.setAttribute('fetchpriority', 'low');
|
|
372
|
+
photo.width = 34;
|
|
373
|
+
photo.height = 34;
|
|
374
|
+
photo.onerror = () => {
|
|
375
|
+
photo.src = FALLBACK_THUMB_URL;
|
|
320
376
|
};
|
|
377
|
+
avatarBubble.appendChild(photo);
|
|
321
378
|
|
|
379
|
+
const nameBubble = document.createElement('span');
|
|
380
|
+
nameBubble.className = 'nav-user-name-bubble';
|
|
381
|
+
|
|
382
|
+
const icon = document.createElement('i');
|
|
383
|
+
icon.className = 'fa-solid fa-user nav-user-icon';
|
|
384
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
385
|
+
|
|
386
|
+
const name = document.createElement('span');
|
|
387
|
+
name.className = 'nav-user-name';
|
|
388
|
+
name.textContent = resolvedName;
|
|
389
|
+
|
|
390
|
+
nameBubble.append(icon, name);
|
|
391
|
+
authLink.append(avatarBubble, nameBubble);
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
return runAfterLoadIdle(() => {
|
|
322
395
|
fetch('/api/sticker-packs/auth/google/session', { credentials: 'include' })
|
|
323
396
|
.then((response) => {
|
|
324
397
|
if (!response.ok) throw new Error('Sessão indisponível');
|
|
@@ -335,23 +408,25 @@ function HomeEffects() {
|
|
|
335
408
|
.catch(() => {
|
|
336
409
|
showLoginButton();
|
|
337
410
|
});
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
useEffect(() => {
|
|
341
|
-
const wppButton = document.getElementById('wpp-float');
|
|
342
|
-
if (!wppButton) return;
|
|
343
|
-
|
|
344
|
-
const command = 'iniciar';
|
|
345
|
-
const normalizeDigits = (value) => String(value || '').replace(/\D+/g, '');
|
|
346
|
-
const buildUrl = (phone) => `https://wa.me/${phone}?text=${encodeURIComponent(command)}`;
|
|
347
|
-
const applyLink = (phone) => {
|
|
348
|
-
const digits = normalizeDigits(phone);
|
|
349
|
-
if (!digits) return false;
|
|
350
|
-
wppButton.href = buildUrl(digits);
|
|
351
|
-
wppButton.hidden = false;
|
|
352
|
-
return true;
|
|
353
|
-
};
|
|
411
|
+
}, { delayMs: 200, timeoutMs: 1400 });
|
|
412
|
+
};
|
|
354
413
|
|
|
414
|
+
const initWhatsappFloatingButton = () => {
|
|
415
|
+
const wppButton = document.getElementById('wpp-float');
|
|
416
|
+
if (!wppButton) return null;
|
|
417
|
+
|
|
418
|
+
const command = 'iniciar';
|
|
419
|
+
const normalizeDigits = (value) => String(value || '').replace(/\D+/g, '');
|
|
420
|
+
const buildUrl = (phone) => `https://wa.me/${phone}?text=${encodeURIComponent(command)}`;
|
|
421
|
+
const applyLink = (phone) => {
|
|
422
|
+
const digits = normalizeDigits(phone);
|
|
423
|
+
if (!digits) return false;
|
|
424
|
+
wppButton.href = buildUrl(digits);
|
|
425
|
+
wppButton.hidden = false;
|
|
426
|
+
return true;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
return runAfterLoadIdle(() => {
|
|
355
430
|
fetch('/api/sticker-packs?visibility=public&limit=1')
|
|
356
431
|
.then((response) => {
|
|
357
432
|
if (!response.ok) throw new Error('Falha ao buscar bot');
|
|
@@ -365,52 +440,54 @@ function HomeEffects() {
|
|
|
365
440
|
.catch(() => {
|
|
366
441
|
wppButton.hidden = true;
|
|
367
442
|
});
|
|
368
|
-
},
|
|
369
|
-
|
|
370
|
-
useEffect(() => {
|
|
371
|
-
const cpuEl = document.getElementById('metric-host-cpu');
|
|
372
|
-
const memEl = document.getElementById('metric-host-memory');
|
|
373
|
-
const uptimeEl = document.getElementById('metric-process-uptime');
|
|
374
|
-
const obsEl = document.getElementById('metric-observability');
|
|
375
|
-
const proofUsers = document.getElementById('proof-users');
|
|
376
|
-
const proofGroups = document.getElementById('proof-groups');
|
|
377
|
-
const proofSystem = document.getElementById('proof-system');
|
|
378
|
-
if (!cpuEl || !memEl || !uptimeEl || !obsEl) return;
|
|
379
|
-
|
|
380
|
-
const normalizeStatus = (value) => {
|
|
381
|
-
const normalized = String(value || '')
|
|
382
|
-
.trim()
|
|
383
|
-
.toLowerCase();
|
|
384
|
-
if (!normalized) return 'degraded';
|
|
385
|
-
if (['online', 'healthy', 'ok'].includes(normalized)) return 'online';
|
|
386
|
-
if (['offline', 'down', 'disconnected'].includes(normalized)) return 'offline';
|
|
387
|
-
if (['connecting', 'opening', 'reconnecting'].includes(normalized)) return 'connecting';
|
|
388
|
-
return 'degraded';
|
|
389
|
-
};
|
|
443
|
+
}, { delayMs: 500, timeoutMs: 2400 });
|
|
444
|
+
};
|
|
390
445
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
446
|
+
const initSystemSummary = () => {
|
|
447
|
+
const cpuEl = document.getElementById('metric-host-cpu');
|
|
448
|
+
const memEl = document.getElementById('metric-host-memory');
|
|
449
|
+
const uptimeEl = document.getElementById('metric-process-uptime');
|
|
450
|
+
const obsEl = document.getElementById('metric-observability');
|
|
451
|
+
const proofUsers = document.getElementById('proof-users');
|
|
452
|
+
const proofGroups = document.getElementById('proof-groups');
|
|
453
|
+
const proofSystem = document.getElementById('proof-system');
|
|
454
|
+
if (!cpuEl || !memEl || !uptimeEl || !obsEl) return null;
|
|
455
|
+
|
|
456
|
+
const normalizeStatus = (value) => {
|
|
457
|
+
const normalized = String(value || '')
|
|
458
|
+
.trim()
|
|
459
|
+
.toLowerCase();
|
|
460
|
+
if (!normalized) return 'degraded';
|
|
461
|
+
if (['online', 'healthy', 'ok'].includes(normalized)) return 'online';
|
|
462
|
+
if (['offline', 'down', 'disconnected'].includes(normalized)) return 'offline';
|
|
463
|
+
if (['connecting', 'opening', 'reconnecting'].includes(normalized)) return 'connecting';
|
|
464
|
+
return 'degraded';
|
|
465
|
+
};
|
|
397
466
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (proofGroups) proofGroups.textContent = 'n/d';
|
|
405
|
-
if (proofSystem) {
|
|
406
|
-
proofSystem.textContent = 'n/d';
|
|
407
|
-
const card = proofSystem.closest('.proof-card');
|
|
408
|
-
if (card) card.dataset.status = 'degraded';
|
|
409
|
-
}
|
|
410
|
-
};
|
|
467
|
+
const formatSystemStatusLabel = (status) => {
|
|
468
|
+
if (status === 'online') return 'online';
|
|
469
|
+
if (status === 'offline') return 'offline';
|
|
470
|
+
if (status === 'connecting') return 'conectando';
|
|
471
|
+
return 'instável';
|
|
472
|
+
};
|
|
411
473
|
|
|
412
|
-
|
|
474
|
+
const setFallback = () => {
|
|
475
|
+
cpuEl.textContent = 'CPU host: n/d';
|
|
476
|
+
memEl.textContent = 'RAM host: n/d';
|
|
477
|
+
uptimeEl.textContent = 'Uptime processo: n/d';
|
|
478
|
+
obsEl.textContent = 'Observabilidade: API em /api/sticker-packs';
|
|
479
|
+
if (proofUsers) proofUsers.textContent = 'n/d';
|
|
480
|
+
if (proofGroups) proofGroups.textContent = 'n/d';
|
|
481
|
+
if (proofSystem) {
|
|
482
|
+
proofSystem.textContent = 'n/d';
|
|
483
|
+
const card = proofSystem.closest('.proof-card');
|
|
484
|
+
if (card) card.dataset.status = 'degraded';
|
|
485
|
+
}
|
|
486
|
+
};
|
|
413
487
|
|
|
488
|
+
const fmt = (value) => (Number.isFinite(value) ? value.toFixed(2) : 'n/d');
|
|
489
|
+
|
|
490
|
+
return runAfterLoadIdle(() => {
|
|
414
491
|
fetch('/api/sticker-packs/system-summary')
|
|
415
492
|
.then((response) => {
|
|
416
493
|
if (!response.ok) throw new Error('Falha ao carregar métricas');
|
|
@@ -456,47 +533,86 @@ function HomeEffects() {
|
|
|
456
533
|
.catch(() => {
|
|
457
534
|
setFallback();
|
|
458
535
|
});
|
|
459
|
-
},
|
|
460
|
-
|
|
461
|
-
useEffect(() => {
|
|
462
|
-
const revealTargets = Array.from(
|
|
463
|
-
document.querySelectorAll(
|
|
464
|
-
'.hero, .market-preview, .section-title, .grid .card, .api, .rank-panel, .final-cta, .hero-stats .chip, .hero-proof .proof-card',
|
|
465
|
-
),
|
|
466
|
-
);
|
|
467
|
-
if (!revealTargets.length) return undefined;
|
|
468
|
-
|
|
469
|
-
revealTargets.forEach((element) => element.classList.add('reveal'));
|
|
470
|
-
if (typeof IntersectionObserver !== 'function') {
|
|
471
|
-
revealTargets.forEach((element) => element.classList.add('in-view'));
|
|
472
|
-
return undefined;
|
|
473
|
-
}
|
|
536
|
+
}, { delayMs: 650, timeoutMs: 2600 });
|
|
537
|
+
};
|
|
474
538
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
539
|
+
const initRevealAnimations = () => {
|
|
540
|
+
const rawTargets = Array.from(
|
|
541
|
+
document.querySelectorAll(
|
|
542
|
+
'.market-preview, .section-title, .grid .card, .api, .rank-panel, .final-cta, .hero-stats .chip',
|
|
543
|
+
),
|
|
544
|
+
);
|
|
545
|
+
if (!rawTargets.length) return null;
|
|
546
|
+
|
|
547
|
+
const viewportHeight = window.innerHeight || 0;
|
|
548
|
+
const revealTargets = rawTargets.filter((element) => {
|
|
549
|
+
const rect = element.getBoundingClientRect();
|
|
550
|
+
return rect.top > viewportHeight * 0.7;
|
|
551
|
+
});
|
|
552
|
+
if (!revealTargets.length) return null;
|
|
553
|
+
|
|
554
|
+
revealTargets.forEach((element) => element.classList.add('reveal'));
|
|
555
|
+
if (typeof IntersectionObserver !== 'function') {
|
|
556
|
+
revealTargets.forEach((element) => element.classList.add('in-view'));
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const observer = new IntersectionObserver(
|
|
561
|
+
(entries) => {
|
|
562
|
+
entries.forEach((entry) => {
|
|
563
|
+
if (entry.isIntersecting) {
|
|
564
|
+
entry.target.classList.add('in-view');
|
|
565
|
+
observer.unobserve(entry.target);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
root: null,
|
|
571
|
+
threshold: 0.14,
|
|
572
|
+
rootMargin: '0px 0px -8% 0px',
|
|
573
|
+
},
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
revealTargets.forEach((element) => observer.observe(element));
|
|
577
|
+
return () => observer.disconnect();
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
const registerCleanup = (cleanups, cleanupFn) => {
|
|
581
|
+
if (typeof cleanupFn === 'function') {
|
|
582
|
+
cleanups.push(cleanupFn);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
const initHomeApp = () => {
|
|
587
|
+
if (window.__omnizapHomeAppReady) return;
|
|
588
|
+
window.__omnizapHomeAppReady = true;
|
|
589
|
+
|
|
590
|
+
const cleanups = [];
|
|
591
|
+
registerCleanup(cleanups, initMarketplacePreview());
|
|
592
|
+
registerCleanup(cleanups, initRankingPanel());
|
|
593
|
+
registerCleanup(cleanups, initNavToggle());
|
|
594
|
+
registerCleanup(cleanups, initAuthSession());
|
|
595
|
+
registerCleanup(cleanups, initWhatsappFloatingButton());
|
|
596
|
+
registerCleanup(cleanups, initSystemSummary());
|
|
597
|
+
registerCleanup(cleanups, initRevealAnimations());
|
|
598
|
+
|
|
599
|
+
window.addEventListener(
|
|
600
|
+
'pagehide',
|
|
601
|
+
() => {
|
|
602
|
+
cleanups.forEach((cleanupFn) => {
|
|
603
|
+
try {
|
|
604
|
+
cleanupFn();
|
|
605
|
+
} catch {
|
|
606
|
+
// no-op
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
},
|
|
610
|
+
{ once: true },
|
|
611
|
+
);
|
|
612
|
+
};
|
|
497
613
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
614
|
+
if (document.readyState === 'loading') {
|
|
615
|
+
document.addEventListener('DOMContentLoaded', initHomeApp, { once: true });
|
|
616
|
+
} else {
|
|
617
|
+
initHomeApp();
|
|
502
618
|
}
|