205 lines
8.2 KiB
HTML
205 lines
8.2 KiB
HTML
<!-- OnlyPrompt - Community page:
|
|
- Discover creators, follow/unfollow, dynamic via API -->
|
|
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>OnlyPrompt - Discover Creators</title>
|
|
<link rel="stylesheet" href="../css/variables.css">
|
|
<link rel="stylesheet" href="../css/base.css">
|
|
<link rel="stylesheet" href="../css/sidebar.css">
|
|
<link rel="stylesheet" href="../css/login.css">
|
|
<link rel="stylesheet" href="../css/topbar.css">
|
|
<link rel="stylesheet" href="../css/community.css">
|
|
<script src="../js/profile-shared.js"></script>
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
</head>
|
|
<body>
|
|
<div class="layout" style="display: flex; min-height: 100vh; background: var(--bg);">
|
|
|
|
<div id="sidebar-container"></div>
|
|
|
|
<div style="flex:1; margin:40px auto; max-width:950px;">
|
|
|
|
<div id="topbar-container"></div>
|
|
|
|
<main class="creators-main">
|
|
|
|
<div class="creators-header">
|
|
<h1>Discover Creators</h1>
|
|
<p>Follow your favorite prompt artists and get inspired.</p>
|
|
</div>
|
|
|
|
<div class="filter-buttons">
|
|
<button class="filter-btn active" data-sort="popular">Popular</button>
|
|
<button class="filter-btn" data-sort="prompts">Rising</button>
|
|
<button class="filter-btn" data-sort="new">New</button>
|
|
<button class="filter-btn" data-sort="rating">Top Rated</button>
|
|
</div>
|
|
|
|
<div class="creators-grid" id="creators-grid"></div>
|
|
|
|
<div id="creators-empty" style="display:none; text-align:center; padding:60px 20px; color:#64748b;">
|
|
<i class="bi bi-people" style="font-size:3rem; display:block; margin-bottom:16px;"></i>
|
|
<h3 id="creators-empty-title" style="margin-bottom:8px;">No creators found</h3>
|
|
<p id="creators-empty-text">Check back later for new creators to follow.</p>
|
|
</div>
|
|
|
|
<div id="creators-error" style="display:none; text-align:center; padding:60px 20px; color:#ef4444;">
|
|
<i class="bi bi-exclamation-circle" style="font-size:3rem; display:block; margin-bottom:16px;"></i>
|
|
<h3 style="margin-bottom:8px;">Could not load creators</h3>
|
|
<p id="creators-error-msg"></p>
|
|
</div>
|
|
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module">
|
|
// ── Sidebar & Topbar ─────────────────────────────────────────────
|
|
fetch('/sidebar.html')
|
|
.then(r => r.text())
|
|
.then(data => {
|
|
document.getElementById('sidebar-container').innerHTML = data;
|
|
document.querySelectorAll('#sidebar-container .sidebar a').forEach(l => l.classList.remove('active'));
|
|
const thirdLink = document.querySelectorAll('#sidebar-container .sidebar li a')[2];
|
|
if (thirdLink) thirdLink.classList.add('active');
|
|
});
|
|
fetch('/topbar.html')
|
|
.then(r => r.text())
|
|
.then(data => document.getElementById('topbar-container').innerHTML = data);
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────
|
|
function renderStars(rating) {
|
|
if (!rating) return '';
|
|
const stars = Math.round(rating);
|
|
return `<span style="color:#f59e0b">${'★'.repeat(stars)}${'☆'.repeat(5 - stars)}</span> <span style="color:#64748b;font-size:0.8rem">${rating.toFixed(1)}</span>`;
|
|
}
|
|
|
|
function renderCard(c) {
|
|
return `
|
|
<div class="creator-card">
|
|
<img class="creator-avatar"
|
|
src="${c.avatarUrl || '../images/content/cat.png'}"
|
|
alt="${c.displayName}"
|
|
style="cursor:pointer"
|
|
onclick="location.href='/profile?id=${c.userId}'">
|
|
<div class="creator-info">
|
|
<h3 class="creator-name"
|
|
style="cursor:pointer"
|
|
onclick="location.href='/profile?id=${c.userId}'">${c.displayName}</h3>
|
|
<div class="creator-handle">@${c.slug}</div>
|
|
<p class="creator-bio">${c.bio ?? 'No bio yet.'}</p>
|
|
<div class="creator-stats">
|
|
<span><i class="bi bi-puzzle"></i> ${c.promptCount} prompts</span>
|
|
<span><i class="bi bi-people"></i> ${c.subscribers}</span>
|
|
${c.averageRating > 0 ? `<span>${renderStars(c.averageRating)}</span>` : ''}
|
|
</div>
|
|
<button class="follow-btn ${c.isFollowing ? 'following' : ''}"
|
|
data-userid="${c.userId}"
|
|
data-following="${c.isFollowing}">
|
|
${c.isFollowing ? 'Following' : 'Follow'}
|
|
</button>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
// ── Follow / Unfollow ────────────────────────────────────────────
|
|
async function toggleFollow(btn) {
|
|
const userId = btn.dataset.userid;
|
|
const isFollowing = btn.dataset.following === 'true';
|
|
btn.disabled = true;
|
|
|
|
const res = await fetch(`/api/v1/subscriptions/${userId}`, {
|
|
method: isFollowing ? 'DELETE' : 'PUT',
|
|
credentials: 'same-origin'
|
|
});
|
|
|
|
if (res.status === 401) { location.href = '/login'; return; }
|
|
|
|
if (res.ok) {
|
|
const nowFollowing = !isFollowing;
|
|
btn.dataset.following = nowFollowing;
|
|
btn.textContent = nowFollowing ? 'Following' : 'Follow';
|
|
btn.classList.toggle('following', nowFollowing);
|
|
}
|
|
|
|
btn.disabled = false;
|
|
}
|
|
|
|
// ── Load Creators ────────────────────────────────────────────────
|
|
const grid = document.getElementById('creators-grid');
|
|
const emptyEl = document.getElementById('creators-empty');
|
|
const emptyTitle = document.getElementById('creators-empty-title');
|
|
const emptyText = document.getElementById('creators-empty-text');
|
|
const errorEl = document.getElementById('creators-error');
|
|
const errorMsg = document.getElementById('creators-error-msg');
|
|
|
|
let activeSort = 'popular';
|
|
let currentSearch = new URLSearchParams(location.search).get('search') || '';
|
|
|
|
function getSearchTerm() {
|
|
return currentSearch.trim();
|
|
}
|
|
|
|
async function loadCreators(sort = activeSort) {
|
|
activeSort = sort;
|
|
grid.innerHTML = '';
|
|
emptyEl.style.display = 'none';
|
|
errorEl.style.display = 'none';
|
|
|
|
try {
|
|
const params = new URLSearchParams({
|
|
sort,
|
|
limit: '50'
|
|
});
|
|
const search = getSearchTerm();
|
|
if (search) params.set('search', search);
|
|
|
|
const res = await fetch(`/api/v1/profiles?${params}`);
|
|
if (res.status === 401) { location.href = '/login'; return; }
|
|
if (!res.ok) throw new Error(`Server error ${res.status}`);
|
|
|
|
const creators = await res.json();
|
|
if (creators.length === 0) {
|
|
const search = getSearchTerm();
|
|
emptyTitle.textContent = search ? 'No matching creators' : 'No creators found';
|
|
emptyText.textContent = search
|
|
? `No creator matches "${search}". Try another name or clear the search.`
|
|
: 'Create another local user to see creators here.';
|
|
emptyEl.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
grid.innerHTML = creators.map(renderCard).join('');
|
|
|
|
grid.querySelectorAll('.follow-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => toggleFollow(btn));
|
|
});
|
|
} catch (e) {
|
|
errorEl.style.display = 'block';
|
|
errorMsg.textContent = e.message;
|
|
}
|
|
}
|
|
|
|
// ── Filter buttons ───────────────────────────────────────────────
|
|
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
loadCreators(btn.dataset.sort);
|
|
});
|
|
});
|
|
|
|
window.applyCreatorSearch = (value) => {
|
|
currentSearch = value.trim();
|
|
loadCreators(activeSort);
|
|
};
|
|
|
|
loadCreators();
|
|
</script>
|
|
</body>
|
|
</html>
|