added requesting option

This commit is contained in:
Ludwig Lehnert
2026-03-28 08:43:04 +01:00
parent 12e4bcddc6
commit 48bfe69d09
14 changed files with 730 additions and 5 deletions

View File

@@ -1,4 +1,5 @@
import {
createUploadRequestAction,
deleteOwnUploadAction,
extendOwnUploadAction,
userLogoutAction,
@@ -23,11 +24,20 @@ export default async function DashboardPage({ searchParams }) {
'SELECT id, original_name, stored_name, size_bytes, uploaded_at, expires_at FROM uploads WHERE owner = ? ORDER BY uploaded_at DESC',
[user.username]
);
const uploadRequests = await all(
`SELECT id, note, created_at, expires_at, completed_at, uploaded_original_name
FROM upload_requests
WHERE owner = ?
ORDER BY created_at DESC
LIMIT 200`,
[user.username]
);
const resolvedSearchParams = await searchParams;
const error = readSearchParam(resolvedSearchParams, 'error');
const success = readSearchParam(resolvedSearchParams, 'success');
const totalBytes = uploads.reduce((total, item) => total + (Number(item.size_bytes) || 0), 0);
const nowTs = Date.now();
return (
<main className="page-shell">
@@ -76,6 +86,83 @@ export default async function DashboardPage({ searchParams }) {
</aside>
</div>
<section className="panel">
<h2>Upload-Anfragen</h2>
<p className="muted">Benachrichtigung erfolgt an deinen Benutzernamen (E-Mail-Adresse).</p>
<form className="form-grid" action={createUploadRequestAction}>
<input type="hidden" name="csrfToken" value={csrfToken} />
<label className="field">
Notiz (optional)
<input className="input" name="note" placeholder="z. B. Bitte die Rechnung als PDF senden" />
</label>
<label className="field">
Gültig in Stunden
<input className="input" name="expiresHours" placeholder="72" />
</label>
<button className="btn" type="submit">
Anfrage erstellen
</button>
</form>
{uploadRequests.length === 0 ? (
<p className="muted">Noch keine Upload-Anfragen.</p>
) : (
<div className="table-wrap">
<table>
<thead>
<tr>
<th>Anfrage</th>
<th>Status</th>
<th>Erstellt</th>
<th>Gültig bis</th>
<th>Ergebnis</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{uploadRequests.map((entry) => {
const requestPath = `/_request/${encodeURIComponent(entry.id)}`;
const isCompleted = Number(entry.completed_at || 0) > 0;
const isExpired = !isCompleted && Number(entry.expires_at || 0) <= nowTs;
return (
<tr key={entry.id}>
<td>
<strong>{entry.id}</strong>
{entry.note ? <div className="muted">{entry.note}</div> : null}
</td>
<td>
<span
className={`badge ${
isCompleted ? 'success' : isExpired ? 'danger' : 'primary'
}`}
>
{isCompleted ? 'Abgeschlossen' : isExpired ? 'Abgelaufen' : 'Offen'}
</span>
</td>
<td>{formatTimestamp(entry.created_at)}</td>
<td>{formatTimestamp(entry.expires_at)}</td>
<td>{entry.uploaded_original_name || '-'}</td>
<td>
<div className="row-actions">
<a className="btn secondary" href={requestPath}>
Öffnen
</a>
<CopyLinkButton path={requestPath} label="Upload-Anfrage" />
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
</section>
<section className="panel">
<h2>Aktuelle Uploads</h2>
{uploads.length === 0 ? (