Files
Webklar-Kundenbereich/public/app.js
KNSO f31727aeb4 Implementiere Kundenportal mit zentraler Appwrite-Anbindung.
Express-Server für Appwrite-Auth, Session, Projekt-Dashboard und Gitea-Webhook; statisches Frontend und Schema-Dokumentation für woms-database.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-22 23:38:38 +02:00

143 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
async function api(path, options = {}) {
const response = await fetch(path, {
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) },
...options,
})
const data = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(data.error || `Fehler ${response.status}`)
}
return data
}
function showError(el, message) {
if (!el) return
el.textContent = message
el.classList.remove('hidden')
}
function hideError(el) {
if (el) el.classList.add('hidden')
}
function featuresForProject(features, projectId) {
return features.filter((f) => !f.projectId || f.projectId === projectId)
}
function renderProjectCard(project, features) {
const li = document.createElement('li')
li.className = 'project-card'
const projectFeatures = featuresForProject(features, project.id)
const featureHtml = projectFeatures.length
? `<div class="feature-tags">${projectFeatures.map((f) => `<span class="feature-tag">${escapeHtml(f.featureKey)}</span>`).join('')}</div>`
: '<p class="muted">Keine zusätzlichen Features freigeschaltet.</p>'
li.innerHTML = `
<h2>${escapeHtml(project.projectName || project.subdomain || 'Projekt')}</h2>
<dl>
<dt>Subdomain</dt><dd>${escapeHtml(project.subdomain || '')}</dd>
<dt>Vorschau</dt><dd>${project.previewUrl ? `<a href="${escapeAttr(project.previewUrl)}" target="_blank" rel="noopener">${escapeHtml(project.previewUrl)}</a>` : ''}</dd>
<dt>Live-Domain</dt><dd>${project.liveDomain ? escapeHtml(project.liveDomain) : ''}</dd>
<dt>Status</dt><dd>${escapeHtml(project.status || '')}</dd>
<dt>Bereitstellung</dt><dd>${escapeHtml(project.provisioningStatus || '')}</dd>
</dl>
${featureHtml}
`
return li
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
}
function escapeAttr(str) {
return escapeHtml(str).replace(/'/g, '&#39;')
}
async function initLoginPage() {
const form = document.getElementById('login-form')
const errorEl = document.getElementById('login-error')
const btn = document.getElementById('login-btn')
try {
const me = await api('/api/auth/me')
if (me.customer) {
window.location.href = '/dashboard.html'
return
}
} catch {
/* not logged in */
}
form?.addEventListener('submit', async (e) => {
e.preventDefault()
errorEl.classList.add('hidden')
btn.disabled = true
try {
await api('/api/auth/login', {
method: 'POST',
body: JSON.stringify({
email: document.getElementById('email').value,
password: document.getElementById('password').value,
}),
})
window.location.href = '/dashboard.html'
} catch (err) {
showError(errorEl, err.message)
} finally {
btn.disabled = false
}
})
}
async function initDashboardPage() {
const meta = document.getElementById('customer-meta')
const list = document.getElementById('projects')
const loading = document.getElementById('loading')
const empty = document.getElementById('empty')
const loadError = document.getElementById('load-error')
const logoutBtn = document.getElementById('logout-btn')
logoutBtn?.addEventListener('click', async () => {
await api('/api/auth/logout', { method: 'POST' })
window.location.href = '/login.html'
})
try {
const [{ customer }, { projects }, { features }] = await Promise.all([
api('/api/auth/me'),
api('/api/projects'),
api('/api/features'),
])
meta.textContent = customer.name ? `${customer.name} (${customer.email})` : customer.email
loading.classList.add('hidden')
if (!projects.length) {
empty.classList.remove('hidden')
return
}
list.classList.remove('hidden')
list.innerHTML = ''
for (const project of projects) {
list.appendChild(renderProjectCard(project, features))
}
} catch (err) {
loading.classList.add('hidden')
if (err.message.includes('401') || err.message.includes('Nicht angemeldet')) {
window.location.href = '/login.html'
return
}
showError(loadError, err.message)
}
}
window.initLoginPage = initLoginPage
window.initDashboardPage = initDashboardPage