Add user list to admin panel and validated/guest badges on posts
Admin panel: new "Käyttäjät" section showing all registered users with post count, likes count, email and join date. Posts: submissions by logged-in users show a green "Vahvistettu" badge, while guest submissions show a random code (e.g. #L01U51) for tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
43
admin.html
43
admin.html
@@ -159,6 +159,14 @@
|
||||
.cat-info { flex: 1; font-family: Arial, sans-serif; font-size: 0.88rem; color: #3b2a1a; }
|
||||
.cat-info span { color: #7a5c3e; font-size: 0.78rem; }
|
||||
|
||||
/* User list */
|
||||
.user-table { width: 100%; border-collapse: collapse; font-family: Arial, sans-serif; font-size: 0.88rem; }
|
||||
.user-table th { text-align: left; color: #7a5c3e; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.5px; padding: 6px 10px; border-bottom: 2px solid #e8d5c0; }
|
||||
.user-table td { padding: 9px 10px; border-bottom: 1px solid #f0e8dc; color: #3b2a1a; }
|
||||
.user-table tr:last-child td { border-bottom: none; }
|
||||
.user-table .user-nick { font-weight: bold; color: #7c4a1e; }
|
||||
.user-table .badge { display: inline-block; background: #f0e8dc; color: #7a5c3e; padding: 2px 8px; border-radius: 10px; font-size: 0.78rem; }
|
||||
|
||||
.empty-state { text-align: center; color: #7a5c3e; font-style: italic; padding: 20px 0; }
|
||||
|
||||
.toast {
|
||||
@@ -340,9 +348,15 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: Categories + Post list -->
|
||||
<!-- RIGHT: Users + Categories + Post list -->
|
||||
<div class="section-right">
|
||||
|
||||
<!-- USERS -->
|
||||
<section class="admin-panel">
|
||||
<h2>Käyttäjät</h2>
|
||||
<div id="userList"><p class="empty-state">Ladataan...</p></div>
|
||||
</section>
|
||||
|
||||
<!-- CATEGORIES -->
|
||||
<section class="admin-panel">
|
||||
<h2 id="lbl_categories">Kategoriat</h2>
|
||||
@@ -454,17 +468,20 @@
|
||||
}
|
||||
|
||||
async function loadAdminData() {
|
||||
const [postsData, catsData] = await Promise.all([
|
||||
const [postsData, catsData, usersData] = await Promise.all([
|
||||
apiGet('posts'),
|
||||
apiGet('categories'),
|
||||
apiGet('admin_users'),
|
||||
]);
|
||||
ADMIN.posts = postsData.posts || [];
|
||||
ADMIN.categories = catsData.categories || [];
|
||||
ADMIN.users = usersData.users || [];
|
||||
document.getElementById('backLink').textContent = at('back');
|
||||
document.getElementById('saveBtn').textContent = at('save');
|
||||
populateCategorySelect();
|
||||
renderCatList();
|
||||
renderPostList();
|
||||
renderUserList();
|
||||
}
|
||||
|
||||
// ===========================
|
||||
@@ -803,6 +820,28 @@
|
||||
});
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// USER LIST
|
||||
// ===========================
|
||||
function renderUserList() {
|
||||
const users = ADMIN.users || [];
|
||||
const el = document.getElementById('userList');
|
||||
if (!users.length) { el.innerHTML = '<p class="empty-state">Ei rekisteröityneitä käyttäjiä.</p>'; return; }
|
||||
el.innerHTML = `
|
||||
<table class="user-table">
|
||||
<thead><tr><th>Nimimerkki</th><th>Sähköposti</th><th>Julkaisut</th><th>Tykkäykset</th><th>Liittynyt</th></tr></thead>
|
||||
<tbody>${users.map(u => `
|
||||
<tr>
|
||||
<td class="user-nick">${u.nickname}</td>
|
||||
<td>${u.email || '<span style="color:#bbb">–</span>'}</td>
|
||||
<td><span class="badge">${u.postCount}</span></td>
|
||||
<td><span class="badge">${u.likes}</span></td>
|
||||
<td>${u.created || '–'}</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>`;
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// TOAST
|
||||
// ===========================
|
||||
|
||||
22
api.php
22
api.php
@@ -843,6 +843,28 @@ switch ($action) {
|
||||
case 'admin_check':
|
||||
ok(['loggedIn' => isAdmin()]);
|
||||
|
||||
case 'admin_users':
|
||||
if (!isAdmin()) err('Unauthorized', 403);
|
||||
$users = readData('users.json', []);
|
||||
$posts = getOrInitPosts();
|
||||
$result = [];
|
||||
foreach ($users as $u) {
|
||||
$nick = mb_strtolower($u['nickname']);
|
||||
$postCount = 0;
|
||||
foreach ($posts as $p) {
|
||||
if (mb_strtolower($p['author'] ?? '') === $nick) $postCount++;
|
||||
}
|
||||
$result[] = [
|
||||
'id' => $u['id'],
|
||||
'nickname' => $u['nickname'],
|
||||
'email' => $u['email'] ?? '',
|
||||
'created' => $u['created'] ?? '',
|
||||
'likes' => count($u['likes'] ?? []),
|
||||
'postCount' => $postCount,
|
||||
];
|
||||
}
|
||||
ok(['users' => $result]);
|
||||
|
||||
// ─── Käyttäjätunnukset ─────────────────────────────────────
|
||||
case 'user_register':
|
||||
$nickname = trim($body['nickname'] ?? '');
|
||||
|
||||
@@ -213,7 +213,7 @@ function renderCards() {
|
||||
<div class="card-body">
|
||||
<span class="category-tag">${getCategoryLabel(p.category)}</span>
|
||||
<h3>${p.title}</h3>
|
||||
${p.author ? `<p class="card-author">✍️ ${p.author}</p>` : ''}
|
||||
${p.author ? `<p class="card-author">✍️ ${p.author}${p.submittedBy?.validated ? ' <span class="validated-badge">✔ Vahvistettu</span>' : (p.submittedBy?.guestCode ? ` <span class="guest-badge">#${p.submittedBy.guestCode}</span>` : '')}</p>` : ''}
|
||||
<p>${p.desc || ''}</p>
|
||||
${metaRow}
|
||||
<div class="card-actions">
|
||||
@@ -326,7 +326,7 @@ async function openPost(id) {
|
||||
<p class="modal-meta">
|
||||
📂 ${getCategoryLabel(p.category)}
|
||||
${p.time ? ` | ⏱ ${p.time} | 👤 ${p.servings}` : ''}
|
||||
${p.author ? ` | ✍️ ${t('modal_by')} ${p.author}` : ''}
|
||||
${p.author ? ` | ✍️ ${t('modal_by')} ${p.author}${p.submittedBy?.validated ? ' <span class="validated-badge">✔ Vahvistettu</span>' : (p.submittedBy?.guestCode ? ` <span class="guest-badge">#${p.submittedBy.guestCode}</span>` : '')}` : ''}
|
||||
</p>
|
||||
<div class="modal-like-row">
|
||||
<button class="like-btn ${liked ? 'liked' : ''}" data-like-id="${p.id}" onclick="toggleLike('${p.id}')">${liked ? t('liked_btn') : t('like_btn')}</button>
|
||||
@@ -590,10 +590,15 @@ async function submitPublicPost() {
|
||||
document.getElementById('sub-img3').value.trim(),
|
||||
].filter(Boolean);
|
||||
|
||||
const submittedBy = APP.user
|
||||
? { userId: APP.user.id, nickname: APP.user.nickname, validated: true }
|
||||
: { guestCode: Math.random().toString(36).slice(2, 8).toUpperCase(), validated: false };
|
||||
|
||||
const post = {
|
||||
id: title.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/\s+/g,'_').replace(/[^a-z0-9_]/g,'') + '_' + Date.now(),
|
||||
title, emoji, category, subcategory, author, desc, images,
|
||||
type: submitType,
|
||||
submittedBy,
|
||||
};
|
||||
|
||||
if (submitType === 'recipe') {
|
||||
|
||||
24
style.css
24
style.css
@@ -632,6 +632,30 @@ footer {
|
||||
/* =====================
|
||||
CARD AUTHOR + ACTIONS
|
||||
===================== */
|
||||
.validated-badge {
|
||||
display: inline-block;
|
||||
background: #d4edda;
|
||||
color: #2d6a3f;
|
||||
font-size: 0.72rem;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
padding: 1px 7px;
|
||||
border-radius: 10px;
|
||||
margin-left: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.guest-badge {
|
||||
display: inline-block;
|
||||
background: #f0e8dc;
|
||||
color: #7a5c3e;
|
||||
font-size: 0.72rem;
|
||||
font-style: normal;
|
||||
font-family: monospace;
|
||||
padding: 1px 7px;
|
||||
border-radius: 10px;
|
||||
margin-left: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.card-author {
|
||||
font-size: 0.8rem !important;
|
||||
color: var(--light-brown) !important;
|
||||
|
||||
Reference in New Issue
Block a user