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 && {setShareItem(null);load();}} toast={toast}/>} - {publicShareItem && {setPublicShareItem(null);api('/tools/dateien/public-shares').then(d=>setPublicShares(d.shares||[])).catch(()=>{});}} toast={toast}/>} + {publicShareItem && {setPublicShareItem(null);api('/tools/dateien/public-shares').then(d=>setPublicShares(d.shares||[])).catch(()=>{});}} onShareCreated={(token,pw)=>setSharePws(p=>({...p,[token]:pw}))} toast={toast}/>} {/* ── Aktive öffentliche Links ──────────────────────────────────────── */} {publicShares.length > 0 && tab === 'own' && ( @@ -779,6 +781,16 @@ export default function Dateien({ toast, mobile }) { style={{background:'rgba(74,222,128,0.1)',border:'1px solid rgba(74,222,128,0.3)',borderRadius:5,padding:'4px 8px',color:'#4ade80',fontFamily:'monospace',fontSize:9,cursor:'pointer',flexShrink:0}}> 📋 + {sharePws[s.token] && ( + + )}