fix: public_file_shares Tabelle in db.js, google.de Redirect bei Fehler, saubere Router-Trennung
This commit is contained in:
@@ -2907,48 +2907,38 @@ function PublicFileShare({ token }) {
|
||||
const [dlLoading,setDlLoading]= useState(null);
|
||||
|
||||
const S2 = {
|
||||
wrap: { minHeight:'100vh', background:'#0f1117', display:'flex', alignItems:'center', justifyContent:'center', padding:20 },
|
||||
box: { background:'#1a1d2e', border:'1px solid rgba(255,255,255,0.08)', borderRadius:16, padding:32, maxWidth:480, width:'100%' },
|
||||
head: { color:'#fff', fontFamily:'monospace', fontSize:18, fontWeight:700, marginBottom:6 },
|
||||
sub: { color:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:12, marginBottom:24 },
|
||||
label: { color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:11, marginBottom:6, display:'block' },
|
||||
inp: { width:'100%', boxSizing:'border-box', background:'rgba(255,255,255,0.06)', border:'1px solid rgba(255,255,255,0.12)', borderRadius:8, padding:'10px 14px', color:'#fff', fontFamily:'monospace', fontSize:13, outline:'none' },
|
||||
btn: (c='#4ecdc4') => ({ background:`${c}18`, border:`1px solid ${c}44`, borderRadius:8, padding:'10px 16px', color:c, fontFamily:'monospace', fontSize:13, cursor:'pointer', width:'100%', marginTop:10 }),
|
||||
fileRow:{ display:'flex', alignItems:'center', gap:12, padding:'10px 14px', background:'rgba(255,255,255,0.04)', border:'1px solid rgba(255,255,255,0.07)', borderRadius:8, marginBottom:8 },
|
||||
err: { color:'#f87171', fontFamily:'monospace', fontSize:12, marginTop:8 },
|
||||
wrap: { minHeight:'100vh', background:'#0f1117', display:'flex', alignItems:'center', justifyContent:'center', padding:20 },
|
||||
box: { background:'#1a1d2e', border:'1px solid rgba(255,255,255,0.08)', borderRadius:16, padding:32, maxWidth:480, width:'100%' },
|
||||
head: { color:'#fff', fontFamily:'monospace', fontSize:18, fontWeight:700, marginBottom:6 },
|
||||
sub: { color:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:12, marginBottom:24 },
|
||||
inp: { width:'100%', boxSizing:'border-box', background:'rgba(255,255,255,0.06)', border:'1px solid rgba(255,255,255,0.12)', borderRadius:8, padding:'10px 14px', color:'#fff', fontFamily:'monospace', fontSize:13, outline:'none' },
|
||||
btn: (c='#4ecdc4') => ({ background:`${c}18`, border:`1px solid ${c}44`, borderRadius:8, padding:'10px 16px', color:c, fontFamily:'monospace', fontSize:13, cursor:'pointer', width:'100%', marginTop:10 }),
|
||||
fileRow: { display:'flex', alignItems:'center', gap:12, padding:'10px 14px', background:'rgba(255,255,255,0.04)', border:'1px solid rgba(255,255,255,0.07)', borderRadius:8, marginBottom:8 },
|
||||
err: { color:'#f87171', fontFamily:'monospace', fontSize:12, marginTop:8 },
|
||||
};
|
||||
|
||||
const BASE = '/api/public/file-share/' + token;
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/public/file-share/' + token)
|
||||
fetch(BASE)
|
||||
.then(async r => {
|
||||
const d = await r.json();
|
||||
if (!r.ok) { setErrMsg(d.error || 'Link ungültig'); setPhase('error'); return; }
|
||||
setInfo(d);
|
||||
setPhase(d.needs_password ? 'pw' : 'ready');
|
||||
if (!d.needs_password && d.is_folder) loadFolder('');
|
||||
setPhase('pw'); // Passwort immer Pflicht
|
||||
})
|
||||
.catch(() => { setErrMsg('Verbindung fehlgeschlagen'); setPhase('error'); });
|
||||
}, [token]);
|
||||
|
||||
async function loadFolder(pw2) {
|
||||
const res = await fetch('/api/public/file-share/' + token + '/folder', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ password: pw2 }),
|
||||
});
|
||||
const d = await res.json();
|
||||
if (!res.ok) { setErrMsg(d.error); return; }
|
||||
setFiles(d.files || []);
|
||||
setPhase('ready');
|
||||
}
|
||||
|
||||
async function handlePw() {
|
||||
if (!pw.trim()) return;
|
||||
const res = await fetch('/api/public/file-share/' + token + (info?.is_folder ? '/folder' : '/download'), {
|
||||
const url = BASE + (info?.is_folder ? '/folder' : '/download');
|
||||
const res = await fetch(url, {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ password: pw }),
|
||||
});
|
||||
const d = await res.json();
|
||||
if (!res.ok) { setErrMsg(d.error); return; }
|
||||
if (!res.ok) { setErrMsg(d.error || 'Falsches Passwort'); return; }
|
||||
setPassword(pw);
|
||||
setErrMsg('');
|
||||
if (info?.is_folder) { setFiles(d.files || []); setPhase('ready'); }
|
||||
@@ -2958,16 +2948,17 @@ function PublicFileShare({ token }) {
|
||||
async function downloadFile(fileId, fileName) {
|
||||
setDlLoading(fileId || 'single');
|
||||
try {
|
||||
const res = await fetch(
|
||||
'/api/public/file-share/' + token + (fileId ? '/folder/' + fileId : '/download'),
|
||||
{ method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ password }) }
|
||||
);
|
||||
const url = BASE + (fileId ? '/folder/' + fileId : '/download');
|
||||
const res = await fetch(url, {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ password }),
|
||||
});
|
||||
if (!res.ok) { const d = await res.json(); setErrMsg(d.error); return; }
|
||||
const blob = await res.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a'); a.href = url; a.download = fileName;
|
||||
const objUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a'); a.href = objUrl; a.download = fileName;
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
||||
setTimeout(() => URL.revokeObjectURL(url), 5000);
|
||||
setTimeout(() => URL.revokeObjectURL(objUrl), 5000);
|
||||
} finally { setDlLoading(null); }
|
||||
}
|
||||
|
||||
@@ -2978,43 +2969,50 @@ function PublicFileShare({ token }) {
|
||||
return (b/1024/1024).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
// Abgelaufen/ungültig → Redirect zu google.de
|
||||
if (phase === 'error') {
|
||||
window.location.replace('https://www.google.de');
|
||||
return <div style={S2.wrap}><div style={{color:'#fff',fontFamily:'monospace'}}>Weiterleitung…</div></div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={S2.wrap}>
|
||||
<div style={S2.box}>
|
||||
<div style={S2.head}>🔗 {info?.is_folder ? '📁 Ordner' : '📄 Datei'} – Freigabe</div>
|
||||
<div style={S2.sub}>
|
||||
{info?.folder_name || info?.file_name || info?.label || 'Öffentlicher Link'}
|
||||
{info?.expires_at && <span style={{marginLeft:8}}>· Gültig bis {new Date(info.expires_at*1000).toLocaleDateString('de-DE')}</span>}
|
||||
{info?.expires_at && (
|
||||
<span style={{marginLeft:8}}>
|
||||
· Gültig bis {new Date(info.expires_at*1000).toLocaleDateString('de-DE')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{phase === 'loading' && <div style={S2.sub}>Lade...</div>}
|
||||
|
||||
{phase === 'error' && <div style={S2.err}>⚠️ {errMsg}</div>}
|
||||
|
||||
{phase === 'pw' && (
|
||||
<>
|
||||
<span style={S2.label}>Passwort</span>
|
||||
<input type="password" value={pw} onChange={e=>setPw(e.target.value)}
|
||||
<span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11,marginBottom:6,display:'block'}}>Passwort</span>
|
||||
<input value={pw} onChange={e=>setPw(e.target.value)}
|
||||
onKeyDown={e=>e.key==='Enter'&&handlePw()} autoFocus
|
||||
placeholder="Passwort eingeben" style={S2.inp} />
|
||||
{errMsg && <div style={S2.err}>{errMsg}</div>}
|
||||
{errMsg && <div style={S2.err}>⚠ {errMsg}</div>}
|
||||
<button onClick={handlePw} style={S2.btn('#4ecdc4')}>🔓 Entsperren</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{phase === 'ready' && !info?.is_folder && (
|
||||
<>
|
||||
<div style={{ ...S2.fileRow, marginBottom:16 }}>
|
||||
<div style={{...S2.fileRow, marginBottom:16}}>
|
||||
<span style={{fontSize:24}}>📄</span>
|
||||
<div>
|
||||
<div style={{color:'#fff',fontFamily:'monospace',fontSize:13}}>{info.file_name}</div>
|
||||
<div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:11}}>{fmtSize(info.file_size)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={() => downloadFile(null, info.file_name)}
|
||||
disabled={dlLoading === 'single'}
|
||||
<button onClick={() => downloadFile(null, info.file_name)} disabled={dlLoading==='single'}
|
||||
style={S2.btn('#4ade80')}>
|
||||
{dlLoading === 'single' ? '⏳ Lädt...' : '⬇ Datei herunterladen'}
|
||||
{dlLoading==='single' ? '⏳ Lädt...' : '⬇ Datei herunterladen'}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@@ -3031,14 +3029,13 @@ function PublicFileShare({ token }) {
|
||||
<div style={{color:'#fff',fontFamily:'monospace',fontSize:12}}>{f.name}</div>
|
||||
<div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>{fmtSize(f.size)}</div>
|
||||
</div>
|
||||
<button onClick={() => downloadFile(f.id, f.name)}
|
||||
disabled={!!dlLoading}
|
||||
<button onClick={()=>downloadFile(f.id, f.name)} disabled={!!dlLoading}
|
||||
style={{...S2.btn('#4ade80'), width:'auto', marginTop:0, padding:'6px 14px', fontSize:11}}>
|
||||
{dlLoading === f.id ? '⏳' : '⬇'}
|
||||
{dlLoading===f.id ? '⏳' : '⬇'}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
{files.length === 0 && <div style={S2.sub}>Ordner ist leer.</div>}
|
||||
{files.length===0 && <div style={S2.sub}>Ordner ist leer.</div>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -3046,6 +3043,7 @@ function PublicFileShare({ token }) {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function PublicUpload({ token }) {
|
||||
const [phase, setPhase] = useState('loading');
|
||||
const [info, setInfo] = useState(null);
|
||||
|
||||
Reference in New Issue
Block a user