@kaikybrofc/omnizap-system 2.3.5 → 2.3.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/.env.example +541 -468
- package/.github/workflows/codeql.yml +101 -0
- package/README.md +1 -1
- package/ml/clip_classifier/requirements.txt +2 -2
- package/package.json +1 -1
- package/public/js/apps/stickersApp.js +145 -319
|
@@ -22,6 +22,7 @@ const CATALOG_SORT_OPTIONS = [
|
|
|
22
22
|
|
|
23
23
|
const DEFAULT_CATALOG_SORT = 'trending';
|
|
24
24
|
const DEFAULT_CREATORS_SORT = 'popular';
|
|
25
|
+
const FIRST_CATALOG_PAGE = 1;
|
|
25
26
|
|
|
26
27
|
const safeNumber = (value, fallback = 0) => {
|
|
27
28
|
const numeric = Number(value);
|
|
@@ -250,17 +251,6 @@ const shortNum = (value) => {
|
|
|
250
251
|
return String(n);
|
|
251
252
|
};
|
|
252
253
|
|
|
253
|
-
const formatFullNum = (value) =>
|
|
254
|
-
new Intl.NumberFormat('pt-BR', {
|
|
255
|
-
maximumFractionDigits: 0,
|
|
256
|
-
}).format(Math.max(0, Math.round(Number(value || 0))));
|
|
257
|
-
|
|
258
|
-
const toMiniBars = (values = [], { minHeight = 3, maxHeight = 16 } = {}) => {
|
|
259
|
-
const source = Array.isArray(values) && values.length ? values.map((value) => Math.max(0, Number(value || 0))) : [0, 0, 0, 0, 0, 0, 0];
|
|
260
|
-
const max = Math.max(...source, 1);
|
|
261
|
-
return source.map((value) => Math.max(minHeight, Math.min(maxHeight, Math.round((value / max) * (maxHeight - 2)) + 2)));
|
|
262
|
-
};
|
|
263
|
-
|
|
264
254
|
const getPackEngagement = (pack) => {
|
|
265
255
|
const engagement = pack?.engagement || {};
|
|
266
256
|
const likeCount = Number(engagement.like_count || 0);
|
|
@@ -280,6 +270,8 @@ const getAvatarUrl = (name) => `https://api.dicebear.com/8.x/thumbs/svg?seed=${e
|
|
|
280
270
|
|
|
281
271
|
const parseCatalogSearchState = (search = '') => {
|
|
282
272
|
const params = new URLSearchParams(String(search || ''));
|
|
273
|
+
const pageParam = Number.parseInt(String(params.get('page') || ''), 10);
|
|
274
|
+
const page = Number.isFinite(pageParam) && pageParam > 0 ? pageParam : FIRST_CATALOG_PAGE;
|
|
283
275
|
const filter = String(params.get('filter') || '')
|
|
284
276
|
.trim()
|
|
285
277
|
.toLowerCase();
|
|
@@ -294,6 +286,7 @@ const parseCatalogSearchState = (search = '') => {
|
|
|
294
286
|
return {
|
|
295
287
|
q,
|
|
296
288
|
category,
|
|
289
|
+
page,
|
|
297
290
|
filter: hasTrendingFilter ? 'trending' : '',
|
|
298
291
|
sort: hasTrendingFilter ? 'trending' : normalizeCatalogSort(params.get('sort') || ''),
|
|
299
292
|
};
|
|
@@ -419,12 +412,6 @@ const isRecent = (dateString) => {
|
|
|
419
412
|
|
|
420
413
|
const sleep = (ms) => new Promise((resolve) => window.setTimeout(resolve, Math.max(0, Number(ms) || 0)));
|
|
421
414
|
|
|
422
|
-
const primaryTag = (item) => {
|
|
423
|
-
const tags = Array.isArray(item?.tags) ? item.tags : [];
|
|
424
|
-
if (!tags.length) return '';
|
|
425
|
-
return String(tags[0] || '').replace(/-/g, ' ');
|
|
426
|
-
};
|
|
427
|
-
|
|
428
415
|
const tagLabel = (tag) => {
|
|
429
416
|
const normalized = String(tag || '').toLowerCase();
|
|
430
417
|
if (normalized.includes('nsfw')) return `🔞 ${String(tag).replace(/-/g, ' ').toUpperCase()}`;
|
|
@@ -534,65 +521,6 @@ function PackCard({ pack, index, onOpen, hasNsfwAccess = true, onRequireLogin })
|
|
|
534
521
|
`;
|
|
535
522
|
}
|
|
536
523
|
|
|
537
|
-
function CatalogMetricCard({ label, value = '', valueRaw = null, numberFormat = 'compact', icon = '📊', hint = '', bars = [], tone = 'slate' }) {
|
|
538
|
-
const toneMap = {
|
|
539
|
-
slate: 'border-slate-800 bg-slate-900/60',
|
|
540
|
-
emerald: 'border-emerald-500/20 bg-emerald-500/5',
|
|
541
|
-
cyan: 'border-cyan-500/20 bg-cyan-500/5',
|
|
542
|
-
amber: 'border-amber-500/20 bg-amber-500/5',
|
|
543
|
-
};
|
|
544
|
-
const numericTarget = valueRaw === null || valueRaw === undefined ? null : Math.max(0, Number(valueRaw || 0));
|
|
545
|
-
const [animatedValue, setAnimatedValue] = useState(numericTarget ?? 0);
|
|
546
|
-
const animatedFromRef = useRef(numericTarget ?? 0);
|
|
547
|
-
|
|
548
|
-
useEffect(() => {
|
|
549
|
-
if (numericTarget === null || !Number.isFinite(numericTarget)) return undefined;
|
|
550
|
-
const startValue = Number.isFinite(animatedFromRef.current) ? animatedFromRef.current : 0;
|
|
551
|
-
const endValue = numericTarget;
|
|
552
|
-
if (startValue === endValue) {
|
|
553
|
-
setAnimatedValue(endValue);
|
|
554
|
-
animatedFromRef.current = endValue;
|
|
555
|
-
return undefined;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
let frameId = 0;
|
|
559
|
-
const startAt = performance.now();
|
|
560
|
-
const durationMs = 520;
|
|
561
|
-
const tick = (now) => {
|
|
562
|
-
const progress = Math.min(1, (now - startAt) / durationMs);
|
|
563
|
-
const eased = 1 - Math.pow(1 - progress, 3);
|
|
564
|
-
const next = startValue + (endValue - startValue) * eased;
|
|
565
|
-
setAnimatedValue(next);
|
|
566
|
-
if (progress < 1) {
|
|
567
|
-
frameId = window.requestAnimationFrame(tick);
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
animatedFromRef.current = endValue;
|
|
571
|
-
setAnimatedValue(endValue);
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
frameId = window.requestAnimationFrame(tick);
|
|
575
|
-
return () => {
|
|
576
|
-
if (frameId) window.cancelAnimationFrame(frameId);
|
|
577
|
-
animatedFromRef.current = endValue;
|
|
578
|
-
};
|
|
579
|
-
}, [numericTarget]);
|
|
580
|
-
|
|
581
|
-
const resolvedValue = numericTarget === null ? String(value || '0') : numberFormat === 'full' ? formatFullNum(animatedValue) : shortNum(animatedValue);
|
|
582
|
-
|
|
583
|
-
return html`
|
|
584
|
-
<article className=${`rounded-xl border p-2.5 ${toneMap[tone] || toneMap.slate}`} title=${hint || label}>
|
|
585
|
-
<div className="flex items-center justify-between gap-2">
|
|
586
|
-
<span className="text-sm">${icon}</span>
|
|
587
|
-
<div className="flex items-end gap-0.5">${(Array.isArray(bars) ? bars : []).slice(0, 7).map((bar, index) => html` <span key=${index} className="w-1 rounded-full bg-white/15" style=${{ height: `${Math.max(4, Math.min(16, Number(bar || 0)))}px` }}></span> `)}</div>
|
|
588
|
-
</div>
|
|
589
|
-
<p className="mt-1 text-base font-bold text-slate-100">${resolvedValue}</p>
|
|
590
|
-
<p className="text-[11px] text-slate-400">${label}</p>
|
|
591
|
-
${hint ? html`<p className="mt-0.5 text-[10px] text-slate-500">${hint}</p>` : null}
|
|
592
|
-
</article>
|
|
593
|
-
`;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
524
|
function DiscoverPackRowItem({ pack, onOpen, rank = 0, hasNsfwAccess = true, onRequireLogin }) {
|
|
597
525
|
if (!pack?.pack_key) return null;
|
|
598
526
|
const lockedByNsfw = isPackMarkedNsfw(pack) && !hasNsfwAccess;
|
|
@@ -1467,25 +1395,6 @@ function CreatorProfileDashboard({ googleAuthConfig, googleAuth, googleAuthBusy,
|
|
|
1467
1395
|
`;
|
|
1468
1396
|
}
|
|
1469
1397
|
|
|
1470
|
-
function OrphanCard({ sticker, hasNsfwAccess = true, onRequireLogin }) {
|
|
1471
|
-
const lockedByNsfw = isStickerMarkedNsfw(sticker) && !hasNsfwAccess;
|
|
1472
|
-
return html`
|
|
1473
|
-
<article className="group rounded-2xl border border-slate-700/80 bg-slate-800/70 shadow-soft overflow-hidden transition-all duration-200 hover:-translate-y-0.5">
|
|
1474
|
-
<div className="relative aspect-square bg-slate-900 overflow-hidden">
|
|
1475
|
-
<img src=${lockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : sticker.url || DEFAULT_STICKER_PLACEHOLDER_URL} alt="Sticker sem pack" className=${`w-full h-full object-contain transition-transform duration-300 ${lockedByNsfw ? 'blur-md scale-105' : 'group-hover:scale-110'}`} loading="lazy" />
|
|
1476
|
-
${lockedByNsfw
|
|
1477
|
-
? html`
|
|
1478
|
-
<div className="absolute inset-0 flex items-center justify-center bg-slate-950/35 p-2">
|
|
1479
|
-
<button type="button" onClick=${() => onRequireLogin?.()} className="inline-flex h-8 items-center rounded-lg border border-amber-400/35 bg-amber-500/15 px-2.5 text-[10px] font-semibold text-amber-100">Entrar para desbloquear</button>
|
|
1480
|
-
</div>
|
|
1481
|
-
`
|
|
1482
|
-
: null}
|
|
1483
|
-
</div>
|
|
1484
|
-
<div className="p-2">${primaryTag(sticker) ? html`<span className="inline-flex rounded-full border border-slate-600 bg-slate-900/80 px-2 py-0.5 text-[10px] text-slate-300">${primaryTag(sticker)}</span>` : html`<span className="inline-flex rounded-full border border-slate-600 bg-slate-900/80 px-2 py-0.5 text-[10px] text-slate-400">sticker</span>`}</div>
|
|
1485
|
-
</article>
|
|
1486
|
-
`;
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
1398
|
function SkeletonGrid({ count = 10 }) {
|
|
1490
1399
|
return html`
|
|
1491
1400
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-3">
|
|
@@ -1853,10 +1762,8 @@ function StickersApp() {
|
|
|
1853
1762
|
() => ({
|
|
1854
1763
|
webPath: root?.dataset.webPath || '/stickers',
|
|
1855
1764
|
apiBasePath: root?.dataset.apiBasePath || '/api/sticker-packs',
|
|
1856
|
-
orphanApiPath: root?.dataset.orphanApiPath || '/api/sticker-packs/orphan-stickers',
|
|
1857
1765
|
loginPath: root?.dataset.loginPath || '/login',
|
|
1858
1766
|
limit: parseIntSafe(root?.dataset.defaultLimit, 24),
|
|
1859
|
-
orphanLimit: parseIntSafe(root?.dataset.defaultOrphanLimit, 24),
|
|
1860
1767
|
}),
|
|
1861
1768
|
[root],
|
|
1862
1769
|
);
|
|
@@ -1869,6 +1776,7 @@ function StickersApp() {
|
|
|
1869
1776
|
const [sortBy, setSortBy] = useState(normalizeCatalogSort(initialCatalogSearch.sort || DEFAULT_CATALOG_SORT));
|
|
1870
1777
|
const [activeCategory, setActiveCategory] = useState(initialCatalogSearch.category || '');
|
|
1871
1778
|
const [catalogFilter, setCatalogFilter] = useState(initialCatalogSearch.filter || '');
|
|
1779
|
+
const [catalogPage, setCatalogPage] = useState(Math.max(FIRST_CATALOG_PAGE, Number(initialCatalogSearch.page || FIRST_CATALOG_PAGE)));
|
|
1872
1780
|
const [discoverTab, setDiscoverTab] = useState('growing');
|
|
1873
1781
|
const [showAutocomplete, setShowAutocomplete] = useState(false);
|
|
1874
1782
|
const [recentSearches, setRecentSearches] = useState([]);
|
|
@@ -1876,16 +1784,10 @@ function StickersApp() {
|
|
|
1876
1784
|
const [sortPickerBusy, setSortPickerBusy] = useState(false);
|
|
1877
1785
|
|
|
1878
1786
|
const [packs, setPacks] = useState([]);
|
|
1879
|
-
const [packOffset, setPackOffset] = useState(0);
|
|
1880
1787
|
const [packHasMore, setPackHasMore] = useState(true);
|
|
1881
1788
|
const [packsLoading, setPacksLoading] = useState(false);
|
|
1882
|
-
const [packsLoadingMore, setPacksLoadingMore] = useState(false);
|
|
1883
|
-
|
|
1884
|
-
const [orphans, setOrphans] = useState([]);
|
|
1885
|
-
const [orphansLoading, setOrphansLoading] = useState(false);
|
|
1886
1789
|
|
|
1887
1790
|
const [error, setError] = useState('');
|
|
1888
|
-
const [sentinel, setSentinel] = useState(null);
|
|
1889
1791
|
|
|
1890
1792
|
const [currentView, setCurrentView] = useState(initialRoute.view || 'catalog');
|
|
1891
1793
|
const [currentPackKey, setCurrentPackKey] = useState(initialRoute.packKey || '');
|
|
@@ -1954,9 +1856,6 @@ function StickersApp() {
|
|
|
1954
1856
|
const scoreBoost = 1 + engagement.openCount * 0.02 + engagement.likeCount * 0.08;
|
|
1955
1857
|
(Array.isArray(pack?.tags) ? pack.tags : []).forEach((tag) => ensureTag(tag, scoreBoost));
|
|
1956
1858
|
});
|
|
1957
|
-
orphans.forEach((asset) => {
|
|
1958
|
-
(Array.isArray(asset?.tags) ? asset.tags : []).forEach((tag) => ensureTag(tag, 1));
|
|
1959
|
-
});
|
|
1960
1859
|
|
|
1961
1860
|
const sortedTags = Array.from(scoreByTag.entries())
|
|
1962
1861
|
.sort((a, b) => b[1] - a[1])
|
|
@@ -1974,7 +1873,7 @@ function StickersApp() {
|
|
|
1974
1873
|
}
|
|
1975
1874
|
|
|
1976
1875
|
return [{ value: '', label: '🔥 Em alta', icon: '🔥' }, ...sortedTags];
|
|
1977
|
-
}, [packs,
|
|
1876
|
+
}, [packs, activeCategory]);
|
|
1978
1877
|
|
|
1979
1878
|
const tagSuggestions = useMemo(() => {
|
|
1980
1879
|
const options = new Map();
|
|
@@ -2003,12 +1902,9 @@ function StickersApp() {
|
|
|
2003
1902
|
packs.forEach((pack) => {
|
|
2004
1903
|
(Array.isArray(pack?.tags) ? pack.tags : []).forEach(addTag);
|
|
2005
1904
|
});
|
|
2006
|
-
orphans.forEach((sticker) => {
|
|
2007
|
-
(Array.isArray(sticker?.tags) ? sticker.tags : []).forEach(addTag);
|
|
2008
|
-
});
|
|
2009
1905
|
|
|
2010
1906
|
return Array.from(options.values());
|
|
2011
|
-
}, [dynamicCategoryOptions, packs
|
|
1907
|
+
}, [dynamicCategoryOptions, packs]);
|
|
2012
1908
|
|
|
2013
1909
|
const filteredSuggestions = useMemo(() => {
|
|
2014
1910
|
const q = normalizeToken(query);
|
|
@@ -2148,24 +2044,6 @@ function StickersApp() {
|
|
|
2148
2044
|
list.sort((a, b) => b.creatorScore - a.creatorScore);
|
|
2149
2045
|
return list;
|
|
2150
2046
|
}, [creatorRanking, creatorSort]);
|
|
2151
|
-
const platformStats = useMemo(() => {
|
|
2152
|
-
const totals = packs.reduce(
|
|
2153
|
-
(acc, pack) => {
|
|
2154
|
-
const engagement = getPackEngagement(pack);
|
|
2155
|
-
acc.stickers += Number(pack?.sticker_count || 0);
|
|
2156
|
-
acc.opens += engagement.openCount;
|
|
2157
|
-
acc.likes += engagement.likeCount;
|
|
2158
|
-
return acc;
|
|
2159
|
-
},
|
|
2160
|
-
{ stickers: 0, opens: 0, likes: 0 },
|
|
2161
|
-
);
|
|
2162
|
-
return {
|
|
2163
|
-
packs: packs.length,
|
|
2164
|
-
stickers: totals.stickers + orphans.length,
|
|
2165
|
-
opens: totals.opens,
|
|
2166
|
-
likes: totals.likes,
|
|
2167
|
-
};
|
|
2168
|
-
}, [packs, orphans.length]);
|
|
2169
2047
|
const recentPublishedPacks = useMemo(
|
|
2170
2048
|
() =>
|
|
2171
2049
|
[...packs]
|
|
@@ -2177,102 +2055,10 @@ function StickersApp() {
|
|
|
2177
2055
|
.slice(0, 10),
|
|
2178
2056
|
[packs],
|
|
2179
2057
|
);
|
|
2180
|
-
const localTrendBars = useMemo(() => {
|
|
2181
|
-
const sample = topWeekPacks.slice(0, 7).map((pack) => {
|
|
2182
|
-
const engagement = getPackEngagement(pack);
|
|
2183
|
-
return Number(engagement.openCount || 0) + Number(engagement.likeCount || 0) * 2;
|
|
2184
|
-
});
|
|
2185
|
-
return toMiniBars(sample);
|
|
2186
|
-
}, [topWeekPacks]);
|
|
2187
|
-
const globalMarketplaceSeries = useMemo(() => {
|
|
2188
|
-
const rows = Array.isArray(globalMarketplaceStats?.seriesLast7Days) ? globalMarketplaceStats.seriesLast7Days : [];
|
|
2189
|
-
return {
|
|
2190
|
-
packs: rows.map((entry) => safeNumber(entry?.packsPublished)),
|
|
2191
|
-
stickers: rows.map((entry) => safeNumber(entry?.stickersCreated)),
|
|
2192
|
-
clicks: rows.map((entry) => safeNumber(entry?.clicks)),
|
|
2193
|
-
likes: rows.map((entry) => safeNumber(entry?.likes)),
|
|
2194
|
-
};
|
|
2195
|
-
}, [globalMarketplaceStats]);
|
|
2196
|
-
const catalogMetricCards = useMemo(() => {
|
|
2197
|
-
const localTrendingLikes = growingNowPacks.reduce((acc, pack) => acc + getPackEngagement(pack).likeCount, 0);
|
|
2198
|
-
const hasGlobalStats = Boolean(globalMarketplaceStats);
|
|
2199
|
-
const metrics = hasGlobalStats
|
|
2200
|
-
? {
|
|
2201
|
-
packs: safeNumber(globalMarketplaceStats?.totalPacks),
|
|
2202
|
-
stickers: safeNumber(globalMarketplaceStats?.totalStickers),
|
|
2203
|
-
clicks: safeNumber(globalMarketplaceStats?.totalClicks),
|
|
2204
|
-
likes: safeNumber(globalMarketplaceStats?.totalLikes),
|
|
2205
|
-
packsLast7Days: safeNumber(globalMarketplaceStats?.packsLast7Days),
|
|
2206
|
-
stickersWithoutPack: safeNumber(globalMarketplaceStats?.stickersWithoutPack),
|
|
2207
|
-
likesLast7Days: safeNumber(globalMarketplaceStats?.likesLast7Days),
|
|
2208
|
-
bars: {
|
|
2209
|
-
packs: toMiniBars(globalMarketplaceSeries.packs),
|
|
2210
|
-
stickers: toMiniBars(globalMarketplaceSeries.stickers),
|
|
2211
|
-
clicks: toMiniBars(globalMarketplaceSeries.clicks),
|
|
2212
|
-
likes: toMiniBars(globalMarketplaceSeries.likes),
|
|
2213
|
-
},
|
|
2214
|
-
}
|
|
2215
|
-
: {
|
|
2216
|
-
packs: safeNumber(platformStats.packs),
|
|
2217
|
-
stickers: safeNumber(platformStats.stickers),
|
|
2218
|
-
clicks: safeNumber(platformStats.opens),
|
|
2219
|
-
likes: safeNumber(platformStats.likes),
|
|
2220
|
-
packsLast7Days: recentPublishedPacks.slice(0, 7).length,
|
|
2221
|
-
stickersWithoutPack: safeNumber(orphans.length),
|
|
2222
|
-
likesLast7Days: safeNumber(localTrendingLikes),
|
|
2223
|
-
bars: {
|
|
2224
|
-
packs: localTrendBars,
|
|
2225
|
-
stickers: [...localTrendBars].reverse(),
|
|
2226
|
-
clicks: localTrendBars.map((v, i) => Math.max(3, v - (i % 3))),
|
|
2227
|
-
likes: localTrendBars.map((v, i) => Math.max(3, Math.min(16, v - 2 + (i % 2)))),
|
|
2228
|
-
},
|
|
2229
|
-
};
|
|
2230
2058
|
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
label: 'Packs globais',
|
|
2235
|
-
valueRaw: metrics.packs,
|
|
2236
|
-
numberFormat: 'full',
|
|
2237
|
-
icon: '📦',
|
|
2238
|
-
tone: 'slate',
|
|
2239
|
-
hint: `+${formatFullNum(metrics.packsLast7Days)} esta semana`,
|
|
2240
|
-
bars: metrics.bars.packs,
|
|
2241
|
-
},
|
|
2242
|
-
{
|
|
2243
|
-
key: 'stickers',
|
|
2244
|
-
label: 'Stickers globais',
|
|
2245
|
-
valueRaw: metrics.stickers,
|
|
2246
|
-
numberFormat: 'full',
|
|
2247
|
-
icon: '🧩',
|
|
2248
|
-
tone: 'cyan',
|
|
2249
|
-
hint: `${formatFullNum(metrics.stickersWithoutPack)} sem pack`,
|
|
2250
|
-
bars: metrics.bars.stickers,
|
|
2251
|
-
},
|
|
2252
|
-
{
|
|
2253
|
-
key: 'opens',
|
|
2254
|
-
label: 'Cliques globais',
|
|
2255
|
-
valueRaw: metrics.clicks,
|
|
2256
|
-
numberFormat: 'full',
|
|
2257
|
-
icon: '👁',
|
|
2258
|
-
tone: 'emerald',
|
|
2259
|
-
hint: 'Engajamento total',
|
|
2260
|
-
bars: metrics.bars.clicks,
|
|
2261
|
-
},
|
|
2262
|
-
{
|
|
2263
|
-
key: 'likes',
|
|
2264
|
-
label: 'Likes globais',
|
|
2265
|
-
valueRaw: metrics.likes,
|
|
2266
|
-
numberFormat: 'full',
|
|
2267
|
-
icon: '❤️',
|
|
2268
|
-
tone: 'amber',
|
|
2269
|
-
hint: `+${formatFullNum(metrics.likesLast7Days)} em tendência`,
|
|
2270
|
-
bars: metrics.bars.likes,
|
|
2271
|
-
},
|
|
2272
|
-
];
|
|
2273
|
-
}, [globalMarketplaceStats, globalMarketplaceSeries, platformStats, recentPublishedPacks, orphans.length, growingNowPacks, localTrendBars]);
|
|
2274
|
-
|
|
2275
|
-
const hasAnyResult = packs.length > 0 || orphans.length > 0;
|
|
2059
|
+
const hasAnyResult = packs.length > 0;
|
|
2060
|
+
const canGoCatalogPrev = catalogPage > FIRST_CATALOG_PAGE && !packsLoading;
|
|
2061
|
+
const canGoCatalogNext = packHasMore && !packsLoading;
|
|
2276
2062
|
const marketplaceGlobalStatsApiPath = '/api/marketplace/stats';
|
|
2277
2063
|
const googleSessionApiPath = `${config.apiBasePath}/auth/google/session`;
|
|
2278
2064
|
const myProfileApiPath = `${config.apiBasePath}/me`;
|
|
@@ -2608,19 +2394,12 @@ function StickersApp() {
|
|
|
2608
2394
|
return params;
|
|
2609
2395
|
};
|
|
2610
2396
|
|
|
2611
|
-
const loadPacks = async ({
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
setPackHasMore(true);
|
|
2616
|
-
} else {
|
|
2617
|
-
if (packsLoadingMore || !packHasMore) return;
|
|
2618
|
-
setPacksLoadingMore(true);
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2397
|
+
const loadPacks = async ({ page = catalogPage } = {}) => {
|
|
2398
|
+
const safePage = Math.max(FIRST_CATALOG_PAGE, Number(page || FIRST_CATALOG_PAGE));
|
|
2399
|
+
const nextOffset = (safePage - 1) * config.limit;
|
|
2400
|
+
setPacksLoading(true);
|
|
2621
2401
|
setError('');
|
|
2622
2402
|
try {
|
|
2623
|
-
const nextOffset = reset ? 0 : packOffset;
|
|
2624
2403
|
const effectiveSort = catalogFilter === 'trending' ? 'trending' : sortBy;
|
|
2625
2404
|
const params = buildParams({
|
|
2626
2405
|
q: catalogFilter === 'trending' ? '' : appliedQuery,
|
|
@@ -2634,34 +2413,14 @@ function StickersApp() {
|
|
|
2634
2413
|
const payload = await fetchJson(`${config.apiBasePath}?${params.toString()}`);
|
|
2635
2414
|
const data = Array.isArray(payload?.data) ? payload.data : [];
|
|
2636
2415
|
const hasMore = Boolean(payload?.pagination?.has_more);
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
setPacks((prev) => (reset ? data : prev.concat(data)));
|
|
2416
|
+
setPacks(data);
|
|
2640
2417
|
setPackHasMore(hasMore);
|
|
2641
|
-
setPackOffset(Number.isFinite(next) ? next : reset ? data.length : nextOffset + data.length);
|
|
2642
2418
|
} catch (err) {
|
|
2643
2419
|
setError(err?.message || 'Falha ao carregar packs');
|
|
2644
|
-
|
|
2420
|
+
setPacks([]);
|
|
2421
|
+
setPackHasMore(false);
|
|
2645
2422
|
} finally {
|
|
2646
|
-
|
|
2647
|
-
else setPacksLoadingMore(false);
|
|
2648
|
-
}
|
|
2649
|
-
};
|
|
2650
|
-
|
|
2651
|
-
const loadOrphans = async () => {
|
|
2652
|
-
setOrphansLoading(true);
|
|
2653
|
-
try {
|
|
2654
|
-
const params = buildParams({
|
|
2655
|
-
q: catalogFilter === 'trending' ? '' : appliedQuery,
|
|
2656
|
-
category: catalogFilter === 'trending' ? '' : activeCategory,
|
|
2657
|
-
limit: config.orphanLimit,
|
|
2658
|
-
});
|
|
2659
|
-
const payload = await fetchJson(`${config.orphanApiPath}?${params.toString()}`);
|
|
2660
|
-
setOrphans(Array.isArray(payload?.data) ? payload.data : []);
|
|
2661
|
-
} catch {
|
|
2662
|
-
setOrphans([]);
|
|
2663
|
-
} finally {
|
|
2664
|
-
setOrphansLoading(false);
|
|
2423
|
+
setPacksLoading(false);
|
|
2665
2424
|
}
|
|
2666
2425
|
};
|
|
2667
2426
|
|
|
@@ -2798,8 +2557,9 @@ function StickersApp() {
|
|
|
2798
2557
|
}
|
|
2799
2558
|
};
|
|
2800
2559
|
|
|
2801
|
-
const buildCatalogWebUrl = ({ q = appliedQuery, category = activeCategory, sort = sortBy, filter = catalogFilter } = {}) => {
|
|
2560
|
+
const buildCatalogWebUrl = ({ q = appliedQuery, category = activeCategory, sort = sortBy, filter = catalogFilter, page = catalogPage } = {}) => {
|
|
2802
2561
|
const params = new URLSearchParams();
|
|
2562
|
+
const safePage = Math.max(FIRST_CATALOG_PAGE, Number(page || FIRST_CATALOG_PAGE));
|
|
2803
2563
|
const normalizedFilter =
|
|
2804
2564
|
String(filter || '')
|
|
2805
2565
|
.trim()
|
|
@@ -2814,6 +2574,7 @@ function StickersApp() {
|
|
|
2814
2574
|
if (String(category || '').trim()) params.set('category', String(category).trim().toLowerCase());
|
|
2815
2575
|
if (normalizedSort && normalizedSort !== DEFAULT_CATALOG_SORT) params.set('sort', normalizedSort);
|
|
2816
2576
|
}
|
|
2577
|
+
if (safePage > FIRST_CATALOG_PAGE) params.set('page', String(safePage));
|
|
2817
2578
|
const qs = params.toString();
|
|
2818
2579
|
return `${config.webPath}/${qs ? `?${qs}` : ''}`;
|
|
2819
2580
|
};
|
|
@@ -2824,7 +2585,7 @@ function StickersApp() {
|
|
|
2824
2585
|
return `${config.webPath}/creators?${params.toString()}`;
|
|
2825
2586
|
};
|
|
2826
2587
|
|
|
2827
|
-
const applyCatalogViewState = ({ q = '', category = '', sort = DEFAULT_CATALOG_SORT, filter = '' } = {}) => {
|
|
2588
|
+
const applyCatalogViewState = ({ q = '', category = '', sort = DEFAULT_CATALOG_SORT, filter = '', page = FIRST_CATALOG_PAGE } = {}) => {
|
|
2828
2589
|
const normalizedFilter =
|
|
2829
2590
|
String(filter || '')
|
|
2830
2591
|
.trim()
|
|
@@ -2838,12 +2599,14 @@ function StickersApp() {
|
|
|
2838
2599
|
: String(category || '')
|
|
2839
2600
|
.trim()
|
|
2840
2601
|
.toLowerCase();
|
|
2602
|
+
const nextPage = Math.max(FIRST_CATALOG_PAGE, Number(page || FIRST_CATALOG_PAGE));
|
|
2841
2603
|
|
|
2842
2604
|
setCatalogFilter(normalizedFilter);
|
|
2843
2605
|
setSortBy(normalizedSort);
|
|
2844
2606
|
setQuery(nextQ);
|
|
2845
2607
|
setAppliedQuery(nextQ);
|
|
2846
2608
|
setActiveCategory(nextCategory);
|
|
2609
|
+
setCatalogPage(nextPage);
|
|
2847
2610
|
};
|
|
2848
2611
|
|
|
2849
2612
|
const openPack = (packKey, push = true) => {
|
|
@@ -2863,11 +2626,12 @@ function StickersApp() {
|
|
|
2863
2626
|
setSortPickerOpen(false);
|
|
2864
2627
|
};
|
|
2865
2628
|
|
|
2866
|
-
const openCatalogWithState = ({ q = '', category = '', sort = DEFAULT_CATALOG_SORT, filter = '', push = true } = {}) => {
|
|
2629
|
+
const openCatalogWithState = ({ q = '', category = '', sort = DEFAULT_CATALOG_SORT, filter = '', page = FIRST_CATALOG_PAGE, push = true } = {}) => {
|
|
2867
2630
|
const nextState = {
|
|
2868
2631
|
q,
|
|
2869
2632
|
category,
|
|
2870
2633
|
sort: normalizeCatalogSort(sort || DEFAULT_CATALOG_SORT),
|
|
2634
|
+
page: Math.max(FIRST_CATALOG_PAGE, Number(page || FIRST_CATALOG_PAGE)),
|
|
2871
2635
|
filter:
|
|
2872
2636
|
String(filter || '')
|
|
2873
2637
|
.trim()
|
|
@@ -2885,9 +2649,42 @@ function StickersApp() {
|
|
|
2885
2649
|
setSortPickerOpen(false);
|
|
2886
2650
|
};
|
|
2887
2651
|
|
|
2652
|
+
const goToCatalogPage = (nextPage, { push = true } = {}) => {
|
|
2653
|
+
const safePage = Math.max(FIRST_CATALOG_PAGE, Number(nextPage || FIRST_CATALOG_PAGE));
|
|
2654
|
+
if (safePage === catalogPage && currentView === 'catalog' && !currentPackKey) return;
|
|
2655
|
+
openCatalogWithState({
|
|
2656
|
+
q: catalogFilter === 'trending' ? '' : appliedQuery,
|
|
2657
|
+
category: catalogFilter === 'trending' ? '' : activeCategory,
|
|
2658
|
+
sort: sortBy,
|
|
2659
|
+
filter: catalogFilter,
|
|
2660
|
+
page: safePage,
|
|
2661
|
+
push,
|
|
2662
|
+
});
|
|
2663
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
2664
|
+
};
|
|
2665
|
+
|
|
2666
|
+
const scrollToCatalogPacksSection = ({ behavior = 'smooth' } = {}) => {
|
|
2667
|
+
const tryScroll = () => {
|
|
2668
|
+
const section = document.getElementById('catalog-packs-section');
|
|
2669
|
+
if (!section) return false;
|
|
2670
|
+
section.scrollIntoView({ behavior, block: 'start' });
|
|
2671
|
+
return true;
|
|
2672
|
+
};
|
|
2673
|
+
|
|
2674
|
+
if (tryScroll()) return;
|
|
2675
|
+
let tries = 0;
|
|
2676
|
+
const retry = () => {
|
|
2677
|
+
tries += 1;
|
|
2678
|
+
if (tryScroll() || tries >= 8) return;
|
|
2679
|
+
window.setTimeout(retry, 80);
|
|
2680
|
+
};
|
|
2681
|
+
window.setTimeout(retry, 80);
|
|
2682
|
+
};
|
|
2683
|
+
|
|
2888
2684
|
const openTrendingCatalog = () => {
|
|
2889
2685
|
openCatalogWithState({ q: '', category: '', sort: 'trending', filter: 'trending', push: true });
|
|
2890
2686
|
setDiscoverTab('growing');
|
|
2687
|
+
scrollToCatalogPacksSection({ behavior: 'smooth' });
|
|
2891
2688
|
};
|
|
2892
2689
|
|
|
2893
2690
|
const openCreatorsRanking = (sort = DEFAULT_CREATORS_SORT, push = true) => {
|
|
@@ -3218,11 +3015,11 @@ function StickersApp() {
|
|
|
3218
3015
|
if (sortPickerBusy || sortPickerLockRef.current) return;
|
|
3219
3016
|
sortPickerLockRef.current = true;
|
|
3220
3017
|
setSortPickerBusy(true);
|
|
3221
|
-
const previousScrollY = window.scrollY || 0;
|
|
3222
3018
|
if (catalogFilter) setCatalogFilter('');
|
|
3223
3019
|
setSortBy(nextSort);
|
|
3020
|
+
setCatalogPage(FIRST_CATALOG_PAGE);
|
|
3224
3021
|
setSortPickerOpen(false);
|
|
3225
|
-
|
|
3022
|
+
scrollToCatalogPacksSection({ behavior: 'smooth' });
|
|
3226
3023
|
window.setTimeout(() => {
|
|
3227
3024
|
sortPickerLockRef.current = false;
|
|
3228
3025
|
setSortPickerBusy(false);
|
|
@@ -3426,7 +3223,7 @@ function StickersApp() {
|
|
|
3426
3223
|
if (nextUrl !== currentUrl) {
|
|
3427
3224
|
window.history.replaceState({}, '', nextUrl);
|
|
3428
3225
|
}
|
|
3429
|
-
}, [currentView, currentPackKey, appliedQuery, activeCategory, sortBy, catalogFilter, config.webPath]);
|
|
3226
|
+
}, [currentView, currentPackKey, appliedQuery, activeCategory, sortBy, catalogFilter, catalogPage, config.webPath]);
|
|
3430
3227
|
|
|
3431
3228
|
useEffect(() => {
|
|
3432
3229
|
if (currentView !== 'creators') return;
|
|
@@ -3447,32 +3244,16 @@ function StickersApp() {
|
|
|
3447
3244
|
return;
|
|
3448
3245
|
}
|
|
3449
3246
|
if (currentView !== 'catalog') return;
|
|
3450
|
-
void loadPacks({
|
|
3451
|
-
|
|
3452
|
-
}, [appliedQuery, activeCategory, sortBy, catalogFilter, currentView, currentPackKey, creatorSort]);
|
|
3247
|
+
void loadPacks({ page: catalogPage });
|
|
3248
|
+
}, [appliedQuery, activeCategory, sortBy, catalogFilter, currentView, currentPackKey, creatorSort, catalogPage]);
|
|
3453
3249
|
|
|
3454
3250
|
useEffect(() => {
|
|
3455
3251
|
if (currentView !== 'catalog' || currentPackKey) return undefined;
|
|
3456
3252
|
const timer = setInterval(() => {
|
|
3457
|
-
void loadPacks({
|
|
3458
|
-
void loadOrphans();
|
|
3253
|
+
void loadPacks({ page: catalogPage });
|
|
3459
3254
|
}, 60 * 1000);
|
|
3460
3255
|
return () => clearInterval(timer);
|
|
3461
|
-
}, [currentView, currentPackKey, appliedQuery, activeCategory, sortBy, catalogFilter]);
|
|
3462
|
-
|
|
3463
|
-
useEffect(() => {
|
|
3464
|
-
if (currentView !== 'catalog' || !sentinel || !packHasMore || packsLoading || packsLoadingMore || currentPackKey) return;
|
|
3465
|
-
const observer = new IntersectionObserver(
|
|
3466
|
-
(entries) => {
|
|
3467
|
-
if (entries.some((entry) => entry.isIntersecting)) {
|
|
3468
|
-
void loadPacks({ reset: false });
|
|
3469
|
-
}
|
|
3470
|
-
},
|
|
3471
|
-
{ rootMargin: '220px 0px' },
|
|
3472
|
-
);
|
|
3473
|
-
observer.observe(sentinel);
|
|
3474
|
-
return () => observer.disconnect();
|
|
3475
|
-
}, [sentinel, packHasMore, packsLoading, packsLoadingMore, packOffset, appliedQuery, activeCategory, sortBy, catalogFilter, currentView, currentPackKey]);
|
|
3256
|
+
}, [currentView, currentPackKey, appliedQuery, activeCategory, sortBy, catalogFilter, catalogPage]);
|
|
3476
3257
|
|
|
3477
3258
|
useEffect(() => {
|
|
3478
3259
|
const sync = () => setUploadTask(readUploadTask());
|
|
@@ -3613,6 +3394,7 @@ function StickersApp() {
|
|
|
3613
3394
|
setShowAutocomplete(false);
|
|
3614
3395
|
const next = query.trim();
|
|
3615
3396
|
setCatalogFilter('');
|
|
3397
|
+
setCatalogPage(FIRST_CATALOG_PAGE);
|
|
3616
3398
|
setAppliedQuery(next);
|
|
3617
3399
|
if (next) {
|
|
3618
3400
|
const nextHistory = [next, ...recentSearches.filter((entry) => entry !== next)].slice(0, 8);
|
|
@@ -3628,6 +3410,7 @@ function StickersApp() {
|
|
|
3628
3410
|
if (!value) return;
|
|
3629
3411
|
setQuery(value);
|
|
3630
3412
|
setCatalogFilter('');
|
|
3413
|
+
setCatalogPage(FIRST_CATALOG_PAGE);
|
|
3631
3414
|
setAppliedQuery(value);
|
|
3632
3415
|
if (dynamicCategoryOptions.some((entry) => entry.value === value)) {
|
|
3633
3416
|
setActiveCategory(value);
|
|
@@ -3640,6 +3423,7 @@ function StickersApp() {
|
|
|
3640
3423
|
setAppliedQuery('');
|
|
3641
3424
|
setActiveCategory('');
|
|
3642
3425
|
setCatalogFilter('');
|
|
3426
|
+
setCatalogPage(FIRST_CATALOG_PAGE);
|
|
3643
3427
|
};
|
|
3644
3428
|
|
|
3645
3429
|
return html`
|
|
@@ -3755,7 +3539,7 @@ function StickersApp() {
|
|
|
3755
3539
|
<h3 className="text-xs font-semibold uppercase tracking-wide text-slate-300">Filtros</h3>
|
|
3756
3540
|
<button type="button" onClick=${clearFilters} className="h-8 rounded-lg border border-slate-700 px-2 text-[11px] text-slate-200 hover:bg-slate-800">Limpar</button>
|
|
3757
3541
|
</div>
|
|
3758
|
-
<p className="mt-1 text-[11px] text-slate-500">${packs.length}
|
|
3542
|
+
<p className="mt-1 text-[11px] text-slate-500">${packs.length} packs nesta página</p>
|
|
3759
3543
|
</div>
|
|
3760
3544
|
|
|
3761
3545
|
<details open className="rounded-xl border border-slate-800 bg-slate-950/40 p-2">
|
|
@@ -3784,11 +3568,9 @@ function StickersApp() {
|
|
|
3784
3568
|
<div>
|
|
3785
3569
|
<p className="text-[11px] uppercase tracking-wide text-slate-400">Descobrir</p>
|
|
3786
3570
|
<h3 className="text-sm font-semibold text-slate-100">Painel oficial do marketplace</h3>
|
|
3787
|
-
<p className="text-[11px] text-slate-500">Métricas globais reais da plataforma (cache ~${globalMarketplaceStats?.cacheSeconds || 45}s, atualização automática).</p>
|
|
3788
3571
|
</div>
|
|
3789
3572
|
<div className="flex items-center gap-2">
|
|
3790
3573
|
${globalMarketplaceStatsLoading && !globalMarketplaceStats ? html`<span className="inline-flex h-8 items-center rounded-lg border border-slate-700 bg-slate-900/60 px-3 text-[11px] text-slate-300">Carregando métricas...</span>` : null} ${globalMarketplaceStatsError ? html`<span className="inline-flex h-8 items-center rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 text-[11px] text-amber-100">Fallback local</span>` : null}
|
|
3791
|
-
<a href="/stickers/create/" className="inline-flex h-8 items-center rounded-lg border border-emerald-500/35 bg-emerald-500/10 px-3 text-[11px] font-semibold text-emerald-200 hover:bg-emerald-500/20"> ✨ Criar pack agora </a>
|
|
3792
3574
|
</div>
|
|
3793
3575
|
</div>
|
|
3794
3576
|
|
|
@@ -3826,13 +3608,37 @@ function StickersApp() {
|
|
|
3826
3608
|
<div className="mt-2 lg:hidden">
|
|
3827
3609
|
${discoverTab === 'growing'
|
|
3828
3610
|
? html`
|
|
3829
|
-
<
|
|
3830
|
-
<
|
|
3831
|
-
<
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3611
|
+
<div className="space-y-2">
|
|
3612
|
+
<section className="space-y-1.5">
|
|
3613
|
+
<div className="flex items-center justify-between">
|
|
3614
|
+
<h4 className="text-xs font-semibold text-slate-200">🔥 Em alta agora</h4>
|
|
3615
|
+
<button
|
|
3616
|
+
type="button"
|
|
3617
|
+
onClick=${openTrendingCatalog}
|
|
3618
|
+
className="inline-flex h-7 items-center gap-1 rounded-full border border-cyan-400/35 bg-cyan-500/10 px-2.5 text-[10px] font-semibold text-cyan-100 transition hover:bg-cyan-500/20 active:scale-[0.98]"
|
|
3619
|
+
>
|
|
3620
|
+
<span>ver lista</span>
|
|
3621
|
+
<span aria-hidden="true">↗</span>
|
|
3622
|
+
</button>
|
|
3623
|
+
</div>
|
|
3624
|
+
<div className="flex gap-2 overflow-x-auto pb-1">${growingNowPacks.slice(0, 8).map((entry) => html`<${DiscoverPackMiniCard} key=${`mobile-grow-${entry.pack_key}`} pack=${entry} onOpen=${openPack} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`)}</div>
|
|
3625
|
+
</section>
|
|
3626
|
+
<section className="space-y-1.5">
|
|
3627
|
+
<div className="flex items-center justify-between">
|
|
3628
|
+
<h4 className="text-xs font-semibold text-slate-200">🆕 Recém publicados</h4>
|
|
3629
|
+
<button
|
|
3630
|
+
type="button"
|
|
3631
|
+
onClick=${openSortPicker}
|
|
3632
|
+
disabled=${sortPickerBusy}
|
|
3633
|
+
className="inline-flex h-7 items-center gap-1 rounded-full border border-emerald-400/35 bg-emerald-500/10 px-2.5 text-[10px] font-semibold text-emerald-100 transition hover:bg-emerald-500/20 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100"
|
|
3634
|
+
>
|
|
3635
|
+
<span>ordenar</span>
|
|
3636
|
+
<span aria-hidden="true">⇅</span>
|
|
3637
|
+
</button>
|
|
3638
|
+
</div>
|
|
3639
|
+
<div className="flex gap-2 overflow-x-auto pb-1">${recentPublishedPacks.slice(0, 8).map((entry) => html`<${DiscoverPackMiniCard} key=${`mobile-new-${entry.pack_key}`} pack=${entry} onOpen=${openPack} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`)}</div>
|
|
3640
|
+
</section>
|
|
3641
|
+
</div>
|
|
3836
3642
|
`
|
|
3837
3643
|
: discoverTab === 'top'
|
|
3838
3644
|
? html`
|
|
@@ -3856,18 +3662,6 @@ function StickersApp() {
|
|
|
3856
3662
|
</div>
|
|
3857
3663
|
</div>
|
|
3858
3664
|
|
|
3859
|
-
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">${catalogMetricCards.map((card) => html`<${CatalogMetricCard} key=${card.key} label=${card.label} value=${card.value} valueRaw=${card.valueRaw} numberFormat=${card.numberFormat} icon=${card.icon} hint=${card.hint} bars=${card.bars} tone=${card.tone} />`)}</div>
|
|
3860
|
-
|
|
3861
|
-
<div className="lg:hidden space-y-2">
|
|
3862
|
-
<section className="space-y-1.5">
|
|
3863
|
-
<div className="flex items-center justify-between">
|
|
3864
|
-
<h4 className="text-xs font-semibold text-slate-200">🆕 Recém publicados</h4>
|
|
3865
|
-
<button type="button" onClick=${openSortPicker} disabled=${sortPickerBusy} className="text-[10px] text-cyan-300 disabled:opacity-50">ordenar</button>
|
|
3866
|
-
</div>
|
|
3867
|
-
<div className="flex gap-2 overflow-x-auto pb-1">${recentPublishedPacks.slice(0, 8).map((entry) => html`<${DiscoverPackMiniCard} key=${`mobile-new-${entry.pack_key}`} pack=${entry} onOpen=${openPack} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`)}</div>
|
|
3868
|
-
</section>
|
|
3869
|
-
</div>
|
|
3870
|
-
|
|
3871
3665
|
<div className="hidden lg:block rounded-2xl border border-emerald-500/20 bg-gradient-to-r from-emerald-500/10 to-cyan-500/5 p-2.5">
|
|
3872
3666
|
<div className="flex items-center justify-between gap-3">
|
|
3873
3667
|
<div>
|
|
@@ -3882,11 +3676,11 @@ function StickersApp() {
|
|
|
3882
3676
|
: null}
|
|
3883
3677
|
${packs.length
|
|
3884
3678
|
? html`
|
|
3885
|
-
<section className="space-y-3 min-w-0">
|
|
3679
|
+
<section id="catalog-packs-section" className="space-y-3 min-w-0">
|
|
3886
3680
|
<div className="flex items-end justify-between gap-3">
|
|
3887
3681
|
<div>
|
|
3888
3682
|
<h2 className="text-lg sm:text-xl font-bold">Packs</h2>
|
|
3889
|
-
<p className="text-xs text-slate-400"
|
|
3683
|
+
<p className="text-xs text-slate-400">Página ${catalogPage} · ${sortedPacks.length} resultados · ${categoryActiveLabel}</p>
|
|
3890
3684
|
</div>
|
|
3891
3685
|
<div className="hidden md:flex items-center gap-2">
|
|
3892
3686
|
<span className="text-xs text-slate-400">Ordenar por</span>
|
|
@@ -3896,21 +3690,53 @@ function StickersApp() {
|
|
|
3896
3690
|
</button>
|
|
3897
3691
|
</div>
|
|
3898
3692
|
</div>
|
|
3693
|
+
<div className="flex flex-wrap items-center justify-between gap-2 rounded-xl border border-slate-800 bg-slate-900/60 px-3 py-2">
|
|
3694
|
+
<span className="text-xs text-slate-400">Página ${catalogPage}${packHasMore ? ' · há mais resultados' : ' · fim da lista'}</span>
|
|
3695
|
+
<div className="flex items-center gap-2">
|
|
3696
|
+
<button
|
|
3697
|
+
type="button"
|
|
3698
|
+
onClick=${() => goToCatalogPage(catalogPage - 1, { push: true })}
|
|
3699
|
+
disabled=${!canGoCatalogPrev}
|
|
3700
|
+
className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50"
|
|
3701
|
+
>
|
|
3702
|
+
Anterior
|
|
3703
|
+
</button>
|
|
3704
|
+
<button
|
|
3705
|
+
type="button"
|
|
3706
|
+
onClick=${() => goToCatalogPage(catalogPage + 1, { push: true })}
|
|
3707
|
+
disabled=${!canGoCatalogNext}
|
|
3708
|
+
className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50"
|
|
3709
|
+
>
|
|
3710
|
+
Próxima
|
|
3711
|
+
</button>
|
|
3712
|
+
</div>
|
|
3713
|
+
</div>
|
|
3899
3714
|
<div className="grid min-w-0 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-2.5 sm:gap-3">${sortedPacks.map((pack, index) => html`<div key=${pack.pack_key || pack.id} className="fade-card"><${PackCard} pack=${pack} index=${index} onOpen=${openPack} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} /></div>`)}</div>
|
|
3900
|
-
<div
|
|
3715
|
+
<div className="flex flex-wrap items-center justify-between gap-2 rounded-xl border border-slate-800 bg-slate-900/60 px-3 py-2">
|
|
3716
|
+
<span className="text-xs text-slate-400">Página ${catalogPage}${packHasMore ? ' · há mais resultados' : ' · fim da lista'}</span>
|
|
3717
|
+
<div className="flex items-center gap-2">
|
|
3718
|
+
<button
|
|
3719
|
+
type="button"
|
|
3720
|
+
onClick=${() => goToCatalogPage(catalogPage - 1, { push: true })}
|
|
3721
|
+
disabled=${!canGoCatalogPrev}
|
|
3722
|
+
className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50"
|
|
3723
|
+
>
|
|
3724
|
+
Anterior
|
|
3725
|
+
</button>
|
|
3726
|
+
<button
|
|
3727
|
+
type="button"
|
|
3728
|
+
onClick=${() => goToCatalogPage(catalogPage + 1, { push: true })}
|
|
3729
|
+
disabled=${!canGoCatalogNext}
|
|
3730
|
+
className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50"
|
|
3731
|
+
>
|
|
3732
|
+
Próxima
|
|
3733
|
+
</button>
|
|
3734
|
+
</div>
|
|
3735
|
+
</div>
|
|
3901
3736
|
</section>
|
|
3902
3737
|
`
|
|
3903
3738
|
: null}
|
|
3904
3739
|
${packsLoading ? html`<${SkeletonGrid} count=${10} />` : null} ${!packsLoading && !hasAnyResult ? html`<${EmptyState} onClear=${clearFilters} />` : null}
|
|
3905
|
-
|
|
3906
|
-
<section className="space-y-2.5">
|
|
3907
|
-
<div className="flex items-center justify-between">
|
|
3908
|
-
<h2 className="text-base sm:text-lg font-bold">Stickers sem pack</h2>
|
|
3909
|
-
<span className="text-xs text-slate-400">${orphans.length} resultados</span>
|
|
3910
|
-
</div>
|
|
3911
|
-
|
|
3912
|
-
${orphansLoading ? html`<div className="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-8 gap-2.5 sm:gap-3">${Array.from({ length: 16 }).map((_, i) => html`<div key=${i} className="rounded-2xl border border-slate-700 bg-slate-800 animate-pulse aspect-square"></div>`)}</div>` : html` <div className="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-8 gap-2.5 sm:gap-3">${orphans.map((item) => html`<div key=${item.id} className="fade-card"><${OrphanCard} sticker=${item} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} /></div>`)}</div> `}
|
|
3913
|
-
</section>
|
|
3914
3740
|
</div>
|
|
3915
3741
|
</div>
|
|
3916
3742
|
`}
|