Files
Freundebuch/backend/public/admin/admin.js

246 lines
8.8 KiB
JavaScript
Raw Normal View History

2026-09-07 12:21:53 +02:00
let friends = [];
let shareTargetId = null;
2026-09-07 12:46:58 +02:00
let settings = { publicBaseUrl: '' };
2026-09-07 12:21:53 +02:00
async function checkSession() {
const res = await fetch('/api/session');
const data = await res.json();
if (data.role !== 'admin') {
window.location.href = '/';
}
}
2026-09-07 12:46:58 +02:00
async function loadSettings() {
const res = await fetch('/api/settings');
if (!res.ok) return;
const data = await res.json();
settings = data.settings || { publicBaseUrl: '' };
document.getElementById('publicBaseUrl').value = settings.publicBaseUrl || '';
}
document.getElementById('saveSettingsBtn').addEventListener('click', async () => {
const publicBaseUrl = document.getElementById('publicBaseUrl').value.trim();
const res = await fetch('/api/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ publicBaseUrl }),
});
const data = await res.json();
if (res.ok) {
settings = data.settings;
const msg = document.getElementById('settingsSaved');
msg.textContent = 'Gespeichert ✓';
msg.classList.add('show');
setTimeout(() => msg.classList.remove('show'), 1800);
}
});
function baseUrl() {
return settings.publicBaseUrl && settings.publicBaseUrl.length
? settings.publicBaseUrl
: window.location.origin;
}
2026-09-07 12:21:53 +02:00
async function loadFriends() {
const res = await fetch('/api/friends');
if (res.status === 401) { window.location.href = '/'; return; }
const data = await res.json();
friends = data.friends || [];
renderFriends();
}
function renderFriends() {
const grid = document.getElementById('friendGrid');
const emptyMsg = document.getElementById('emptyMsg');
grid.innerHTML = '';
emptyMsg.style.display = friends.length ? 'none' : 'block';
friends.forEach((f) => {
const el = document.createElement('div');
el.className = 'friend-card';
const thumbHtml = f.profileImage
? `<img class="friend-thumb" src="/uploads/${encodeURIComponent(folderFor(f))}/${encodeURIComponent(f.profileImage)}" />`
: `<div class="friend-thumb placeholder">${(f.name || '?')[0].toUpperCase()}</div>`;
el.innerHTML = `
<div class="tape"></div>
${thumbHtml}
<h3>${escapeHtml(f.name)}</h3>
<span class="badge ${f.shareEnabled ? 'on' : 'off'}">${f.shareEnabled ? 'Freigegeben' : 'Nicht freigegeben'}</span>
<div class="friend-actions">
<a class="btn" href="/friend/${f.id}"> Bearbeiten</a>
<button class="secondary" data-share="${f.id}">🔗 Freigabe</button>
2026-09-07 12:46:58 +02:00
<button class="secondary" data-export="${f.id}"> Export</button>
2026-09-07 12:21:53 +02:00
<button class="danger" data-delete="${f.id}">🗑</button>
</div>
`;
grid.appendChild(el);
});
grid.querySelectorAll('[data-share]').forEach((btn) => {
btn.addEventListener('click', () => openShareModal(btn.dataset.share));
});
grid.querySelectorAll('[data-delete]').forEach((btn) => {
btn.addEventListener('click', () => deleteFriend(btn.dataset.delete));
});
2026-09-07 12:46:58 +02:00
grid.querySelectorAll('[data-export]').forEach((btn) => {
btn.addEventListener('click', () => window.location.href = `/api/friends/${btn.dataset.export}/export`);
});
2026-09-07 12:21:53 +02:00
}
// wir brauchen den vollen Ordnernamen für Bild-URLs; da wir ihn nicht separat liefern,
// nutzen wir /uploads/<id>-Lookup über eine kleine Konvention: Backend akzeptiert auch
// reine IDs nicht direkt, daher holen wir folderName aus friend-detail bei Bedarf.
// Einfachere Lösung: Backend liefert profileImage-Pfad bereits vollständig auf.
function folderFor(f) {
return f.folderName || f.id;
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str || '';
return div.innerHTML;
}
document.getElementById('createForm').addEventListener('submit', async (e) => {
e.preventDefault();
const name = document.getElementById('newName').value.trim();
const errBox = document.getElementById('createErr');
errBox.innerHTML = '';
if (!name) return;
const res = await fetch('/api/friends', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
});
const data = await res.json();
if (!res.ok) {
errBox.innerHTML = `<p class="error-msg">${data.error}</p>`;
return;
}
document.getElementById('newName').value = '';
loadFriends();
});
async function deleteFriend(id) {
const f = friends.find((x) => x.id === id);
if (!confirm(`"${f ? f.name : 'Freund'}" wirklich löschen? Alle Fotos und Einträge gehen dabei verloren.`)) return;
const res = await fetch(`/api/friends/${id}`, { method: 'DELETE' });
if (res.ok) loadFriends();
}
function openShareModal(id) {
shareTargetId = id;
const f = friends.find((x) => x.id === id);
document.getElementById('shareFriendName').textContent = f ? f.name : '';
document.getElementById('sharePw').value = '';
document.getElementById('shareErr').innerHTML = '';
const linkBox = document.getElementById('shareLinkBox');
if (f && f.shareEnabled) {
linkBox.style.display = 'block';
2026-09-07 12:46:58 +02:00
document.getElementById('shareLinkInput').value = `${baseUrl()}/share/${id}`;
2026-09-07 12:21:53 +02:00
} else {
linkBox.style.display = 'none';
}
document.getElementById('shareModal').style.display = 'flex';
}
document.getElementById('shareCloseBtn').addEventListener('click', () => {
document.getElementById('shareModal').style.display = 'none';
});
document.getElementById('shareEnableBtn').addEventListener('click', async () => {
const pw = document.getElementById('sharePw').value;
const errBox = document.getElementById('shareErr');
errBox.innerHTML = '';
const res = await fetch(`/api/friends/${shareTargetId}/share`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: true, password: pw }),
});
const data = await res.json();
if (!res.ok) {
errBox.innerHTML = `<p class="error-msg">${data.error}</p>`;
return;
}
document.getElementById('shareLinkBox').style.display = 'block';
2026-09-07 12:46:58 +02:00
document.getElementById('shareLinkInput').value = `${baseUrl()}/share/${shareTargetId}`;
2026-09-07 12:21:53 +02:00
await loadFriends();
});
document.getElementById('shareDisableBtn').addEventListener('click', async () => {
const res = await fetch(`/api/friends/${shareTargetId}/share`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: false }),
});
if (res.ok) {
document.getElementById('shareLinkBox').style.display = 'none';
await loadFriends();
}
});
document.getElementById('copyLinkBtn').addEventListener('click', () => {
const input = document.getElementById('shareLinkInput');
input.select();
navigator.clipboard.writeText(input.value).catch(() => {});
});
document.getElementById('logoutBtn').addEventListener('click', async () => {
await fetch('/api/logout', { method: 'POST' });
window.location.href = '/';
});
2026-09-07 12:46:58 +02:00
// --- Sichern & Wiederherstellen ---
function showTransferMsg(text, isError) {
const box = document.getElementById('transferMsg');
box.innerHTML = isError ? `<p class="error-msg">${text}</p>` : `<p class="saved-msg2 show">${text}</p>`;
}
document.getElementById('bookExportBtn').addEventListener('click', () => {
window.location.href = '/api/book/export';
});
document.getElementById('bookImportBtn').addEventListener('click', async () => {
const fileInput = document.getElementById('bookImportFile');
const file = fileInput.files[0];
if (!file) { showTransferMsg('Bitte zuerst eine ZIP-Datei auswählen.', true); return; }
const replaceAll = document.getElementById('bookImportReplace').checked;
if (replaceAll && !confirm('Wirklich ALLE bestehenden Freunde vor dem Import löschen?')) return;
const fd = new FormData();
fd.append('file', file);
fd.append('replaceAll', replaceAll ? 'true' : 'false');
showTransferMsg('Importiere...', false);
const res = await fetch('/api/book/import', { method: 'POST', body: fd });
const data = await res.json();
if (res.ok) {
showTransferMsg(`${data.imported.length} Freund(e) importiert: ${data.imported.join(', ') || ''}`, false);
fileInput.value = '';
loadFriends();
} else {
showTransferMsg(data.error || 'Import fehlgeschlagen.', true);
}
});
document.getElementById('friendImportBtn').addEventListener('click', async () => {
const fileInput = document.getElementById('friendImportFile');
const file = fileInput.files[0];
if (!file) { showTransferMsg('Bitte zuerst eine ZIP-Datei auswählen.', true); return; }
const fd = new FormData();
fd.append('file', file);
showTransferMsg('Importiere...', false);
const res = await fetch('/api/friends/import', { method: 'POST', body: fd });
const data = await res.json();
if (res.ok) {
showTransferMsg(`"${data.friend.name}" wurde importiert.`, false);
fileInput.value = '';
loadFriends();
} else {
showTransferMsg(data.error || 'Import fehlgeschlagen.', true);
}
});
2026-09-07 12:21:53 +02:00
checkSession();
2026-09-07 12:46:58 +02:00
loadSettings();
2026-09-07 12:21:53 +02:00
loadFriends();