feat: phase 3 website pages on v1 api
This commit is contained in:
169
app/resources/views/site/home.blade.php
Normal file
169
app/resources/views/site/home.blade.php
Normal file
@@ -0,0 +1,169 @@
|
||||
@extends('site.layout')
|
||||
|
||||
@section('title', 'Dewemoji - Emoji Browser')
|
||||
|
||||
@section('content')
|
||||
<section class="card" style="padding: 18px;">
|
||||
<h1 style="margin: 0 0 6px 0;">Emoji Browser</h1>
|
||||
<p style="margin: 0; color: var(--muted);">Rebuilt website powered by <code>/v1</code> APIs.</p>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 10px; margin-top: 16px;" id="filters">
|
||||
<input id="q" type="text" placeholder="Search (e.g. love, senyum)" style="padding: 10px; border: 1px solid var(--border); border-radius: 10px;">
|
||||
<select id="category" style="padding: 10px; border: 1px solid var(--border); border-radius: 10px;">
|
||||
<option value="">All categories</option>
|
||||
</select>
|
||||
<select id="subcategory" style="padding: 10px; border: 1px solid var(--border); border-radius: 10px;" disabled>
|
||||
<option value="">All subcategories</option>
|
||||
</select>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card" style="margin-top: 14px; padding: 14px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
||||
<strong>Results</strong>
|
||||
<span id="count" style="color: var(--muted); font-size: 13px;">0 / 0</span>
|
||||
</div>
|
||||
|
||||
<div id="grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(94px, 1fr)); gap: 10px;"></div>
|
||||
|
||||
<div style="text-align: center; margin-top: 14px;">
|
||||
<button id="more" style="display:none; border: 1px solid var(--border); background: white; border-radius: 10px; padding: 10px 14px; cursor: pointer;">
|
||||
Load more
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
(() => {
|
||||
const state = {
|
||||
page: 1,
|
||||
limit: 20,
|
||||
total: 0,
|
||||
items: [],
|
||||
categories: {},
|
||||
};
|
||||
|
||||
const qEl = document.getElementById('q');
|
||||
const catEl = document.getElementById('category');
|
||||
const subEl = document.getElementById('subcategory');
|
||||
const grid = document.getElementById('grid');
|
||||
const count = document.getElementById('count');
|
||||
const more = document.getElementById('more');
|
||||
|
||||
function esc(s) {
|
||||
return String(s || '').replace(/[&<>"']/g, (c) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}[c]));
|
||||
}
|
||||
|
||||
async function loadCategories() {
|
||||
const res = await fetch('/v1/categories');
|
||||
const data = await res.json();
|
||||
state.categories = data || {};
|
||||
Object.keys(state.categories).forEach((name) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = name;
|
||||
opt.textContent = name;
|
||||
catEl.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
function renderSubcategories() {
|
||||
const category = catEl.value;
|
||||
const subs = state.categories[category] || [];
|
||||
subEl.innerHTML = '<option value="">All subcategories</option>';
|
||||
subs.forEach((s) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = s;
|
||||
opt.textContent = s;
|
||||
subEl.appendChild(opt);
|
||||
});
|
||||
subEl.disabled = subs.length === 0;
|
||||
}
|
||||
|
||||
async function fetchEmojis(reset = false) {
|
||||
if (reset) {
|
||||
state.page = 1;
|
||||
state.items = [];
|
||||
grid.innerHTML = '';
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: String(state.page),
|
||||
limit: String(state.limit),
|
||||
});
|
||||
|
||||
if (qEl.value.trim()) params.set('q', qEl.value.trim());
|
||||
if (catEl.value) params.set('category', catEl.value);
|
||||
if (subEl.value) params.set('subcategory', subEl.value);
|
||||
|
||||
const res = await fetch('/v1/emojis?' + params.toString());
|
||||
const data = await res.json();
|
||||
state.total = data.total || 0;
|
||||
|
||||
(data.items || []).forEach((item) => state.items.push(item));
|
||||
renderGrid(data.items || [], reset);
|
||||
updateCount();
|
||||
updateMoreButton();
|
||||
}
|
||||
|
||||
function renderGrid(items, reset) {
|
||||
if (reset && items.length === 0) {
|
||||
grid.innerHTML = '<p style="color:var(--muted);grid-column:1/-1;">No emojis found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach((item) => {
|
||||
const card = document.createElement('a');
|
||||
card.href = '/emoji/' + encodeURIComponent(item.slug);
|
||||
card.className = 'card';
|
||||
card.style.cssText = 'padding:10px;text-decoration:none;color:inherit;display:flex;flex-direction:column;align-items:center;gap:6px;';
|
||||
card.innerHTML = `
|
||||
<div style="font-size:30px;">${esc(item.emoji)}</div>
|
||||
<div style="font-size:12px;text-align:center;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:100%">${esc(item.name)}</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function updateCount() {
|
||||
count.textContent = `${state.items.length} / ${state.total}`;
|
||||
}
|
||||
|
||||
function updateMoreButton() {
|
||||
const canLoad = state.items.length < state.total;
|
||||
more.style.display = canLoad ? 'inline-block' : 'none';
|
||||
}
|
||||
|
||||
let timer = null;
|
||||
qEl.addEventListener('input', () => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => fetchEmojis(true), 220);
|
||||
});
|
||||
|
||||
catEl.addEventListener('change', async () => {
|
||||
renderSubcategories();
|
||||
await fetchEmojis(true);
|
||||
});
|
||||
|
||||
subEl.addEventListener('change', () => fetchEmojis(true));
|
||||
|
||||
more.addEventListener('click', async () => {
|
||||
state.page += 1;
|
||||
await fetchEmojis(false);
|
||||
});
|
||||
|
||||
(async () => {
|
||||
await loadCategories();
|
||||
await fetchEmojis(true);
|
||||
})();
|
||||
})();
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
Reference in New Issue
Block a user