feat: harden billing verification and add browse route parity

This commit is contained in:
Dwindi Ramadhana
2026-02-04 08:52:22 +07:00
parent ccec406d6d
commit a4d2031117
20 changed files with 2080 additions and 144 deletions

View File

@@ -176,6 +176,10 @@
<script>
(() => {
const state = { page: 1, limit: 32, total: 0, items: [], categories: {} };
const initialQuery = @json($initialQuery ?? '');
const initialCategory = @json($initialCategory ?? '');
const initialSubcategory = @json($initialSubcategory ?? '');
const initialPath = @json($canonicalPath ?? '/');
const qEl = document.getElementById('q');
const catEl = document.getElementById('category');
const subEl = document.getElementById('subcategory');
@@ -191,8 +195,60 @@
const heroOptional1 = document.getElementById('hero-optional-1');
const heroOptional2 = document.getElementById('hero-optional-2');
const initParams = new URLSearchParams(window.location.search);
if (initParams.get('q')) qEl.value = initParams.get('q');
if (initialQuery) qEl.value = initialQuery;
function slugify(text) {
return String(text || '')
.toLowerCase()
.replaceAll('&', 'and')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
function categoryLabelToSlug(label) {
const map = {
'Smileys & Emotion': 'smileys',
'People & Body': 'people',
'Animals & Nature': 'animals',
'Food & Drink': 'food',
'Travel & Places': 'travel',
'Activities': 'activities',
'Objects': 'objects',
'Symbols': 'symbols',
'Flags': 'flags',
};
return map[String(label || '')] || '';
}
function syncUrl() {
const q = qEl.value.trim();
const cat = catEl.value;
const sub = subEl.value;
const params = new URLSearchParams();
let path = '/';
if (q !== '') {
params.set('q', q);
if (cat) params.set('category', cat);
if (sub) params.set('subcategory', sub);
path = '/';
} else if (cat) {
const catSlug = categoryLabelToSlug(cat);
if (catSlug) {
path = '/' + catSlug;
if (sub) path += '/' + slugify(sub);
} else {
params.set('category', cat);
if (sub) params.set('subcategory', sub);
}
} else {
path = initialPath === '/browse' ? '/browse' : '/';
}
const query = params.toString();
const target = query ? `${path}?${query}` : path;
window.history.replaceState({}, '', target);
}
function esc(s) {
return String(s || '').replace(/[&<>"']/g, (c) => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[c]));
@@ -338,6 +394,7 @@
incoming.forEach((item) => state.items.push(item));
renderGrid(incoming, reset);
updateStats();
syncUrl();
}
function renderGrid(items, reset) {
@@ -399,6 +456,14 @@
(async () => {
await loadCategories();
if (initialCategory && state.categories[initialCategory]) {
catEl.value = initialCategory;
renderSubcategories();
if (initialSubcategory) {
const hasInitialSub = Array.from(subEl.options).some((opt) => opt.value === initialSubcategory);
if (hasInitialSub) subEl.value = initialSubcategory;
}
}
await fetchEmojis(true);
renderTrendingFromItems(state.items);
renderRecent();