diff --git a/frontend/src/tools/dateien.jsx b/frontend/src/tools/dateien.jsx
index ecab45f..42e70e0 100644
--- a/frontend/src/tools/dateien.jsx
+++ b/frontend/src/tools/dateien.jsx
@@ -29,13 +29,18 @@ const IconBtn = ({ onClick, color, title, children, stop }) => (
// ── Share Modal ───────────────────────────────────────────────────────────────
function ShareModal({ item, type, onClose, toast }) {
const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
const [shares, setShares] = useState([]);
const base = type==='folder' ? `/tools/dateien/folders/${item.id}` : `/tools/dateien/${item.id}`;
useEffect(() => { api(`${base}/shares`).then(setShares).catch(()=>{}); }, [base]);
+
const share = async () => {
if (!username.trim()) return;
- try { await api(`${base}/share`,{body:{username:username.trim()}}); toast('Geteilt ✓'); setUsername(''); api(`${base}/shares`).then(setShares); }
- catch(e) { toast(e.message,'error'); }
+ try {
+ await api(`${base}/share`, { body: { username: username.trim(), password: password.trim() || undefined } });
+ toast('Geteilt ✓'); setUsername(''); setPassword('');
+ api(`${base}/shares`).then(setShares);
+ } catch(e) { toast(e.message,'error'); }
};
const unshare = async uid => {
try { await api(`${base}/share/${uid}`,{method:'DELETE'}); setShares(p=>p.filter(s=>s.id!==uid)); toast('Freigabe entfernt'); }
@@ -55,27 +60,57 @@ function ShareModal({ item, type, onClose, toast }) {
-
- setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()}
- placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/>
-
-
+
+ {/* Teilen-Formular */}
+ {type==='file' && (
+
+
+ setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()}
+ placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/>
+
+
+
setPassword(e.target.value)}
+ placeholder="Passwort (optional)" type="text" autoCapitalize="none"
+ style={{...S.inp,fontSize:13,padding:'7px 10px'}}/>
+ {password &&
+ ⚠ Teile das Passwort separat mit dem Empfänger
+
}
+
+ )}
+ {type==='folder' && (
+
+ setUsername(e.target.value)} onKeyDown={e=>e.key==='Enter'&&share()}
+ placeholder="Benutzername" autoCapitalize="none" style={{...S.inp,flex:1,fontSize:15}}/>
+
+
+ )}
+
+ {/* Bestehende Shares */}
{shares.length>0 && (
-
GETEILT MIT
+
GETEILT MIT
{shares.map(s=>(
-
{s.username}
- {s.shared_at &&
seit {fmtDate(s.shared_at)}
}
+
+ {s.username}
+ {s.has_password ? 🔑 : null}
+
+
+ {s.shared_at && `geteilt ${fmtDate(s.shared_at)}`}
+ {s.accessed_at
+ ? ✓ zugegriffen {fmtDate(s.accessed_at)}
+ : ○ noch nicht geöffnet
+ }
+
-
+
))}
)}
- {shares.length===0 && Noch mit niemandem geteilt.
}
+ {shares.length===0 && Noch mit niemandem geteilt.
}
);
@@ -128,17 +163,26 @@ export default function Dateien({ toast, mobile }) {
const onFileInput = e => { doUpload(e.target.files?.[0]); e.target.value=''; };
const onDrop = e => { e.preventDefault(); setDragOver(false); doUpload(e.dataTransfer.files?.[0]); };
- const download = async file => {
+ const download = async (file, pw) => {
try {
- const res = await fetch(`/api/tools/dateien/${file.id}/download`,{
- headers:{Authorization:`Bearer ${localStorage.getItem('sk_token')}`}
- });
- if (!res.ok){const err=await res.json().catch(()=>({error:'Fehler'}));toast(err.error,'error');return;}
+ const url = `/api/tools/dateien/${file.id}/download${pw ? `?password=${encodeURIComponent(pw)}` : ''}`;
+ const res = await fetch(url, { headers:{Authorization:`Bearer ${localStorage.getItem('sk_token')}`} });
+ if (!res.ok) {
+ const err = await res.json().catch(()=>({error:'Fehler'}));
+ if (err.passwordRequired) {
+ const entered = window.prompt(`🔑 Datei "${file.originalname}" ist passwortgeschützt.\nBitte Passwort eingeben:`);
+ if (entered !== null) await download(file, entered);
+ return;
+ }
+ if (pw && res.status===403) { toast('Falsches Passwort', 'error'); return; }
+ toast(err.error,'error'); return;
+ }
const blob = await res.blob();
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a'); a.href=url; a.download=file.originalname;
+ const a = Object.assign(document.createElement('a'), {
+ href: URL.createObjectURL(blob), download: file.originalname,
+ });
document.body.appendChild(a); a.click(); document.body.removeChild(a);
- setTimeout(()=>URL.revokeObjectURL(url),1000);
+ setTimeout(()=>URL.revokeObjectURL(a.href),1000);
} catch(e){toast(e.message,'error');}
};
@@ -345,7 +389,15 @@ export default function Dateien({ toast, mobile }) {
{fmtSize(file.size)} · {fmtDate(file.created_at)}
{tab==='shared'?` · von ${file.owner}`:''}
- {tab==='sharedByMe'&&file.shared_with_names?` · → ${file.shared_with_names}${file.shared_at?' seit '+fmtDate(file.shared_at):''}`:'' }
+ {tab==='sharedByMe'&&file.shared_with_names?(
+
+ {` · → ${file.shared_with_names}`}
+ {file.accessed_at
+ ? · ✓ {fmtDate(file.accessed_at)}
+ : · ○ nicht geöffnet
+ }
+
+ ):''}
{/* Aktionen – gleiche Breite wie bei Ordnern */}