diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 98b7398..8a5ae85 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -2898,7 +2898,7 @@ function AdminPanel({ toast, mobile, user, nav }) {
// ── Öffentliche Datei/Ordner-Download-Seite ────────────────────────────────
function PublicFileShare({ token }) {
- const [phase, setPhase] = useState('loading');
+ const [phase, setPhase] = useState('loading'); // loading|pw|ready|error
const [info, setInfo] = useState(null);
const [files, setFiles] = useState([]);
const [pw, setPw] = useState('');
@@ -2923,19 +2923,24 @@ function PublicFileShare({ token }) {
fetch(BASE)
.then(async r => {
const d = await r.json();
- if (!r.ok) { setErrMsg(d.error || 'Link ungültig'); setPhase('error'); return; }
+ if (!r.ok) { setPhase('error'); return; }
setInfo(d);
- setPhase('pw'); // Passwort immer Pflicht
+ setPhase('pw');
})
- .catch(() => { setErrMsg('Verbindung fehlgeschlagen'); setPhase('error'); });
+ .catch(() => setPhase('error'));
}, [token]);
+ // Sofort redirect ohne Inhalt anzeigen
+ if (phase === 'error') {
+ window.location.replace('https://www.google.de');
+ return null;
+ }
+
async function handlePw() {
if (!pw.trim()) return;
setErrMsg('');
if (info?.is_folder) {
- // Ordner: JSON-Response mit Dateiliste
const res = await fetch(BASE + '/folder', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ password: pw }),
@@ -2946,8 +2951,7 @@ function PublicFileShare({ token }) {
setFiles(d.files || []);
setPhase('ready');
} else {
- // Datei: erst Passwort verifizieren, dann Phase wechseln
- // Download-Endpoint streamt direkt → wir prüfen Content-Type
+ // Datei: erst PW testen via Download (streams bei Erfolg)
const res = await fetch(BASE + '/download', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ password: pw }),
@@ -2957,15 +2961,14 @@ function PublicFileShare({ token }) {
setErrMsg(d.error || 'Falsches Passwort');
return;
}
- // Passwort korrekt → direkt Download starten
- setPassword(pw);
+ // Korrekt → Download starten + Phase wechseln
const blob = await res.blob();
const objUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
- a.href = objUrl;
- a.download = info.file_name || 'download';
+ a.href = objUrl; a.download = info.file_name || 'download';
document.body.appendChild(a); a.click(); document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(objUrl), 5000);
+ setPassword(pw);
setPhase('ready');
}
}
@@ -2994,30 +2997,34 @@ function PublicFileShare({ token }) {
return (b/1024/1024).toFixed(1) + ' MB';
}
- // Abgelaufen/ungültig → sofort zu google.de
- if (phase === 'error') {
- window.location.replace('https://www.google.de');
- return null;
- }
+ // Ablauf formatieren
+ const fmtExp = exp => exp
+ ? new Date(exp*1000).toLocaleDateString('de-DE') + ' ' + new Date(exp*1000).toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'}) + ' Uhr'
+ : null;
return (
-
🔗 {info?.is_folder ? '📁 Ordner' : '📄 Datei'} – Freigabe
-
- {info?.folder_name || info?.file_name || info?.label || 'Öffentlicher Link'}
- {info?.expires_at && (
-
- · Gültig bis {new Date(info.expires_at*1000).toLocaleDateString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric'})} {new Date(info.expires_at*1000).toLocaleTimeString('de-DE', {hour:'2-digit',minute:'2-digit'})} Uhr
-
- )}
-
- {phase === 'loading' &&
Lade...
}
+ {/* Immer sichtbar: Bezeichnung + Ablauf */}
+
🔗 Freigabe
+ {info?.label && (
+
+ {info.label}
+
+ )}
+ {info?.expires_at && (
+
+ Gültig bis {fmtExp(info.expires_at)}
+
+ )}
+ {/* PW-Eingabe */}
{phase === 'pw' && (
<>
-
Passwort
+
+ 🔒 Passwort erforderlich
+
setPw(e.target.value)}
onKeyDown={e=>e.key==='Enter'&&handlePw()} autoFocus
placeholder="Passwort eingeben" style={S2.inp} />
@@ -3026,6 +3033,7 @@ function PublicFileShare({ token }) {
>
)}
+ {/* Datei bereit */}
{phase === 'ready' && !info?.is_folder && (
<>
@@ -3035,20 +3043,18 @@ function PublicFileShare({ token }) {
{fmtSize(info.file_size)}
-
- ✓ Download gestartet
-
>
)}
+ {/* Ordner bereit */}
{phase === 'ready' && info?.is_folder && (
<>
-
- {files.length} Datei{files.length!==1?'en':''} im Ordner
+
+ 📁 {info.folder_name} · {files.length} Datei{files.length!==1?'en':''}
{files.map(f => (
@@ -3066,6 +3072,10 @@ function PublicFileShare({ token }) {
{files.length===0 &&
Ordner ist leer.
}
>
)}
+
+ {phase === 'loading' && (
+
Lade...
+ )}
);
diff --git a/frontend/src/tools/dateien.jsx b/frontend/src/tools/dateien.jsx
index 8ff1309..377c108 100644
--- a/frontend/src/tools/dateien.jsx
+++ b/frontend/src/tools/dateien.jsx
@@ -27,7 +27,7 @@ const IconBtn = ({ onClick, color, title, children, stop }) => (
);
// ── Öffentlicher Link-Share Modal ─────────────────────────────────────────────
-function PublicShareModal({ item, itemType, onClose, toast }) {
+function PublicShareModal({ item, itemType, onClose, onShareCreated, toast }) {
const [password, setPassword] = useState(() => genPw());
const [expiresH, setExpiresH] = useState(1);
const [label, setLabel] = useState('');
@@ -74,6 +74,7 @@ function PublicShareModal({ item, itemType, onClose, toast }) {
setNewLink(link);
setNewPw(password.trim());
setShares(p => [d.share, ...p]);
+ onShareCreated?.(d.token, password.trim());
navigator.clipboard?.writeText(link).catch(()=>{});
toast('Link erstellt & kopiert ✓');
setPassword(genPw());
@@ -594,6 +595,7 @@ export default function Dateien({ toast, mobile }) {
const [publicShareItem, setPublicShareItem] = useState(null);
const [publicShares, setPublicShares] = useState([]);
const [showPublicLinks, setShowPublicLinks] = useState(false);
+ const [sharePws, setSharePws] = useState({}); // {token: plaintext_pw}
const [pwPrompt, setPwPrompt] = useState(null); // {file}
const [uploading, setUploading] = useState(false);
const [dragOver, setDragOver] = useState(false);
@@ -732,7 +734,7 @@ export default function Dateien({ toast, mobile }) {
)}
{shareItem &&