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>
This commit is contained in:
142
public/app.js
Normal file
142
public/app.js
Normal file
@@ -0,0 +1,142 @@
|
||||
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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
function escapeAttr(str) {
|
||||
return escapeHtml(str).replace(/'/g, ''')
|
||||
}
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user