expressjs -> nextjs
This commit is contained in:
149
nextjs/app/manage/dashboard/page.js
Normal file
149
nextjs/app/manage/dashboard/page.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
deleteOwnUploadAction,
|
||||
extendOwnUploadAction,
|
||||
uploadFileAction,
|
||||
userLogoutAction,
|
||||
} from '@/src/lib/actions.js';
|
||||
import { all, runCleanupIfNeeded } from '@/src/lib/db.js';
|
||||
import { formatBytes, formatCountdown, formatTimestamp, readSearchParam } from '@/src/lib/format.js';
|
||||
import { ensureCsrfToken, requireAuthenticatedUser } from '@/src/lib/security.js';
|
||||
|
||||
import { CopyLinkButton } from '../_components/copy-link-button.js';
|
||||
import { StatusMessage } from '../_components/status-message.js';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default async function DashboardPage({ searchParams }) {
|
||||
await runCleanupIfNeeded();
|
||||
|
||||
const user = await requireAuthenticatedUser();
|
||||
const csrfToken = await ensureCsrfToken();
|
||||
const uploads = await all(
|
||||
'SELECT id, original_name, stored_name, size_bytes, uploaded_at, expires_at FROM uploads WHERE owner = ? ORDER BY uploaded_at DESC',
|
||||
[user.username]
|
||||
);
|
||||
|
||||
const resolvedSearchParams = await searchParams;
|
||||
const error = readSearchParam(resolvedSearchParams, 'error');
|
||||
const success = readSearchParam(resolvedSearchParams, 'success');
|
||||
|
||||
return (
|
||||
<main className="page-shell">
|
||||
<header className="page-header">
|
||||
<div className="header-main">
|
||||
<h1>Dateiverwaltung</h1>
|
||||
<p className="muted">Angemeldet als {user.username}</p>
|
||||
</div>
|
||||
|
||||
<div className="toolbar">
|
||||
{user.admin ? (
|
||||
<a className="chip primary" href="/manage/admin/dashboard">
|
||||
Adminbereich
|
||||
</a>
|
||||
) : null}
|
||||
|
||||
<form className="inline-form" action={userLogoutAction}>
|
||||
<input type="hidden" name="csrfToken" value={csrfToken} />
|
||||
<button className="btn secondary" type="submit">
|
||||
Abmelden
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className="panel">
|
||||
<h2>Neue Datei hochladen</h2>
|
||||
<StatusMessage error={error} success={success} />
|
||||
|
||||
<form className="form-grid" action={uploadFileAction} encType="multipart/form-data">
|
||||
<input type="hidden" name="csrfToken" value={csrfToken} />
|
||||
|
||||
<label className="field">
|
||||
Datei
|
||||
<input className="input" name="file" type="file" required />
|
||||
</label>
|
||||
|
||||
<label className="field">
|
||||
Aufbewahrung in Stunden
|
||||
<input className="input" name="retentionHours" placeholder="168" />
|
||||
</label>
|
||||
|
||||
<button className="btn" type="submit">
|
||||
Hochladen
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section className="panel">
|
||||
<h2>Aktuelle Uploads</h2>
|
||||
{uploads.length === 0 ? (
|
||||
<p className="muted">Noch keine Uploads.</p>
|
||||
) : (
|
||||
<div className="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Datei</th>
|
||||
<th>Größe</th>
|
||||
<th>Hochgeladen</th>
|
||||
<th>Ablauf</th>
|
||||
<th>Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{uploads.map((item) => {
|
||||
const sharePath = `/_share/${encodeURIComponent(item.stored_name)}`;
|
||||
return (
|
||||
<tr key={item.id}>
|
||||
<td>
|
||||
<strong>{item.original_name}</strong>
|
||||
<div className="muted mono">{item.stored_name}</div>
|
||||
</td>
|
||||
<td>{formatBytes(item.size_bytes)}</td>
|
||||
<td>{formatTimestamp(item.uploaded_at)}</td>
|
||||
<td>
|
||||
<div>{formatTimestamp(item.expires_at)}</div>
|
||||
<div className="muted">Noch {formatCountdown(item.expires_at)}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="stack-actions">
|
||||
<div className="row-actions">
|
||||
<a className="btn secondary" href={sharePath}>
|
||||
Download
|
||||
</a>
|
||||
<CopyLinkButton path={sharePath} label={item.original_name} />
|
||||
</div>
|
||||
|
||||
<form className="inline-form" action={extendOwnUploadAction}>
|
||||
<input type="hidden" name="csrfToken" value={csrfToken} />
|
||||
<input type="hidden" name="uploadId" value={item.id} />
|
||||
<input
|
||||
className="input small"
|
||||
name="extendHours"
|
||||
placeholder="Stunden"
|
||||
/>
|
||||
<button className="btn" type="submit">
|
||||
Verlängern
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form className="inline-form" action={deleteOwnUploadAction}>
|
||||
<input type="hidden" name="csrfToken" value={csrfToken} />
|
||||
<input type="hidden" name="uploadId" value={item.id} />
|
||||
<button className="btn danger" type="submit">
|
||||
Löschen
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user