Session 2026-05-29: shared dashboard, team tasks page, requests card style, copy/paste files, loading modals, avatar cache bust

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-05-29 15:23:20 -04:00
parent c9998d4a8d
commit 8d0a461e7a
8 changed files with 605 additions and 205 deletions
+81 -34
View File
@@ -161,6 +161,7 @@ export default function FileSharing() {
const [uploading, setUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState(null);
const [downloadingSelection, setDownloadingSelection] = useState(false);
const [downloadProgress, setDownloadProgress] = useState(null);
const [ctxMenu, setCtxMenu] = useState(null);
const [clipboard, setClipboard] = useState(null);
const [hoveredNavPath, setHoveredNavPath] = useState(null);
@@ -420,18 +421,24 @@ export default function FileSharing() {
localStorage.setItem(pinsStorageKey, JSON.stringify(next));
}
function triggerDownload(url, filename) {
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
async function handleDownload(entry) {
const nextPath = entry.path ?? (path === '/' ? `/${entry.name}` : `${path}/${entry.name}`);
try {
const blob = await (await fbqProxy('download', nextPath)).blob();
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = entry.name;
link.click();
URL.revokeObjectURL(url);
} catch {
setError('Download failed.');
triggerDownload(URL.createObjectURL(blob), entry.name);
} catch (err) {
setError(err?.message || 'Download failed.');
}
}
@@ -461,6 +468,7 @@ export default function FileSharing() {
async function handleDownloadSelected() {
if (!selected.size) return;
setDownloadingSelection(true);
setDownloadProgress(0);
setError('');
try {
const selectedEntries = [...selected]
@@ -475,53 +483,42 @@ export default function FileSharing() {
if (!isDir) {
const blob = await (await fbqProxy('download', targetPath)).blob();
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = name;
link.click();
URL.revokeObjectURL(url);
setDownloadProgress(100);
triggerDownload(URL.createObjectURL(blob), name);
return;
}
const zip = new JSZip();
setDownloadProgress(50);
await addPathToZip(zip, targetPath, name);
const blob = await zip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${name}.zip`;
link.click();
URL.revokeObjectURL(url);
setDownloadProgress(100);
triggerDownload(URL.createObjectURL(await zip.generateAsync({ type: 'blob' })), `${name}.zip`);
return;
}
const zip = new JSZip();
for (const entry of selectedEntries) {
for (let i = 0; i < selectedEntries.length; i++) {
const entry = selectedEntries[i];
const name = entry.name ?? entry.filename ?? '';
const targetPath = entry.path ?? (path === '/' ? `/${name}` : `${path}/${name}`);
const isDir = entry.type === 'directory' || entry.isDir;
if (isDir) {
await addPathToZip(zip, targetPath, name);
continue;
} else {
const blob = await (await fbqProxy('download', targetPath)).blob();
zip.file(name, blob);
}
const blob = await (await fbqProxy('download', targetPath)).blob();
zip.file(name, blob);
setDownloadProgress(Math.round(((i + 1) / selectedEntries.length) * 100));
}
const blob = await zip.generateAsync({ type: 'blob' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
const folderName = parsePath(path).at(-1) || 'files';
link.href = url;
link.download = `${folderName}-selection.zip`;
link.click();
URL.revokeObjectURL(url);
} catch {
setError('Download failed.');
triggerDownload(URL.createObjectURL(await zip.generateAsync({ type: 'blob' })), `${folderName}-selection.zip`);
} catch (err) {
setError(err?.message || 'Download failed.');
} finally {
setDownloadingSelection(false);
setDownloadProgress(null);
}
}
@@ -938,6 +935,56 @@ export default function FileSharing() {
</div>
</div>
{(loading || uploading || downloadingSelection) && (
<div
style={{
position: 'fixed',
inset: 0,
zIndex: 1200,
background: 'rgba(0,0,0,0.58)',
backdropFilter: 'blur(6px)',
WebkitBackdropFilter: 'blur(6px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: 24,
}}
>
<div
style={{
background: 'var(--card-bg)',
border: '1px solid var(--border)',
borderRadius: 8,
padding: '18px 21px',
backdropFilter: 'blur(12px)',
WebkitBackdropFilter: 'blur(12px)',
width: 'min(320px, 100%)',
}}
>
<div style={{ fontSize: 11, fontWeight: 500, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 14 }}>
{uploading ? 'Uploading' : downloadingSelection ? 'Downloading' : 'Loading'}
</div>
{(() => {
const pct = uploading ? uploadProgress : downloadingSelection ? downloadProgress : null;
const hasProgress = (uploading || downloadingSelection) && pct !== null;
return (
<>
<div style={{ height: 4, borderRadius: 2, background: 'var(--border)', overflow: 'hidden', marginBottom: hasProgress ? 8 : 0 }}>
<div
className={hasProgress ? undefined : 'file-browser-progress-bar indeterminate'}
style={{ height: '100%', borderRadius: 2, background: 'var(--accent)', width: hasProgress ? `${pct}%` : undefined, transition: 'width 0.2s ease' }}
/>
</div>
{hasProgress && (
<div style={{ fontSize: 12, color: 'var(--text-secondary)', textAlign: 'right' }}>{pct}%</div>
)}
</>
);
})()}
</div>
</div>
)}
{ctxMenu && (() => {
const { x, y, entry, childPath } = ctxMenu;
const isDir = entry ? (entry.type === 'directory' || entry.isDir) : false;