- Add all new Frontend source files (pages, components, hooks, utils) - Add selenium_MHBatchPaymentCheckWorker.py and MHSinglePaymentCheckWorker.py - Add install-steps-5-13.sh setup script - Update .gitignore to exclude runtime/sensitive data (backups, uploads, chat-history, keys, downloads, generated .d.ts files) while keeping folders - Add .gitkeep to preserve empty runtime folders in git Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
148 lines
6.2 KiB
JavaScript
148 lines
6.2 KiB
JavaScript
import { useEffect, useRef, useState } from "react";
|
|
import { useSearch } from "wouter";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Folder as FolderIcon } from "lucide-react";
|
|
import { apiRequest } from "@/lib/queryClient";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { NewFolderModal } from "@/components/cloud-storage/new-folder-modal";
|
|
import FolderPanel from "@/components/cloud-storage/folder-panel";
|
|
import { useAuth } from "@/hooks/use-auth";
|
|
import RecentTopLevelFoldersCard, { recentTopLevelFoldersQueryKey, } from "@/components/cloud-storage/recent-top-level-folder-modal";
|
|
import CloudSearchBar, { cloudSearchQueryKeyRoot, } from "@/components/cloud-storage/search-bar";
|
|
import FilePreviewModal from "@/components/cloud-storage/file-preview-modal";
|
|
import { cloudFilesQueryKeyRoot } from "@/components/cloud-storage/files-section";
|
|
export default function CloudStoragePage() {
|
|
const { toast } = useToast();
|
|
const { user } = useAuth();
|
|
const qc = useQueryClient();
|
|
const search = useSearch();
|
|
// panel open + initial folder id to show when opening
|
|
const [panelOpen, setPanelOpen] = useState(false);
|
|
const [panelInitialFolderId, setPanelInitialFolderId] = useState(null);
|
|
const panelRef = useRef(null);
|
|
// Deep-link: if navigated here with ?folderId=XXX, open that folder automatically
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(search);
|
|
const id = Number(params.get("folderId"));
|
|
if (id > 0) {
|
|
setPanelInitialFolderId(id);
|
|
setPanelOpen(true);
|
|
}
|
|
}, [search]);
|
|
// key to remount recent card to clear its internal selection when needed
|
|
const [recentKey, setRecentKey] = useState(0);
|
|
// New folder modal
|
|
const [isNewFolderOpen, setIsNewFolderOpen] = useState(false);
|
|
// searchbar - file preview modal state
|
|
const [previewFileId, setPreviewFileId] = useState(null);
|
|
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
|
// searchbar - handlers
|
|
function handleOpenFolder(folderId) {
|
|
setPanelInitialFolderId(folderId);
|
|
setPanelOpen(true);
|
|
setTimeout(() => {
|
|
panelRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
}, 50);
|
|
}
|
|
function handleSelectFile(fileId) {
|
|
setPreviewFileId(fileId);
|
|
setIsPreviewOpen(true);
|
|
}
|
|
// create folder handler (page-level)
|
|
async function handleCreateFolder(name) {
|
|
try {
|
|
const userId = user?.id;
|
|
if (!userId) {
|
|
toast({
|
|
title: "Sign in required",
|
|
description: "Please sign in to create a folder.",
|
|
});
|
|
return;
|
|
}
|
|
const res = await apiRequest("POST", `/api/cloud-storage/folders`, {
|
|
userId,
|
|
name,
|
|
parentId: null,
|
|
});
|
|
const json = await res.json();
|
|
if (!res.ok)
|
|
throw new Error(json?.message || "Failed to create folder");
|
|
toast({ title: "Folder created" });
|
|
// close modal
|
|
setIsNewFolderOpen(false);
|
|
// Invalidate recent folders page 1 so RecentFoldersCard will refresh.
|
|
qc.invalidateQueries({ queryKey: recentTopLevelFoldersQueryKey(1) });
|
|
}
|
|
catch (err) {
|
|
toast({ title: "Error", description: err?.message || String(err) });
|
|
}
|
|
}
|
|
return (<div className="container mx-auto space-y-6">
|
|
{/* Header / actions */}
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight">Cloud Storage</h1>
|
|
<p className="text-muted-foreground">
|
|
Manage Files and Folders in Cloud Storage.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex gap-2 items-center">
|
|
<Button onClick={() => setIsNewFolderOpen(true)}>
|
|
<FolderIcon className="h-4 w-4 mr-2"/> New Folder
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<CloudSearchBar onOpenFolder={(folderId) => {
|
|
handleOpenFolder(folderId);
|
|
}} onSelectFile={(fileId) => {
|
|
handleSelectFile(fileId);
|
|
}}/>
|
|
|
|
{/* Recent folders card (delegated component) */}
|
|
<RecentTopLevelFoldersCard key={recentKey} pageSize={10} initialPage={1} onSelect={(folderId) => {
|
|
setPanelInitialFolderId(folderId);
|
|
setPanelOpen(true);
|
|
}}/>
|
|
|
|
{/* FolderPanel lives in page so it can be reused with other UI */}
|
|
<div ref={panelRef}/>
|
|
{panelOpen && (<FolderPanel folderId={panelInitialFolderId} onClose={() => setPanelOpen(false)} onViewChange={(viewedId) => {
|
|
// If the panel navigates back to root, clear recent card selection by remounting it
|
|
if (viewedId === null) {
|
|
setRecentKey((k) => k + 1);
|
|
// clear the panel initial id and close the panel so child folder/file sections hide
|
|
setPanelInitialFolderId(null);
|
|
setPanelOpen(false);
|
|
}
|
|
}}/>)}
|
|
{/* File preview modal */}
|
|
<FilePreviewModal fileId={previewFileId} isOpen={isPreviewOpen} onClose={() => {
|
|
setIsPreviewOpen(false);
|
|
setPreviewFileId(null);
|
|
}} onDeleted={() => {
|
|
// close preview (modal may already close, but be explicit)
|
|
setIsPreviewOpen(false);
|
|
setPreviewFileId(null);
|
|
// refresh the recent folders card
|
|
qc.invalidateQueries({ queryKey: recentTopLevelFoldersQueryKey(1) });
|
|
// invalidate file lists and search results
|
|
qc.invalidateQueries({
|
|
queryKey: cloudFilesQueryKeyRoot,
|
|
exact: false,
|
|
});
|
|
qc.invalidateQueries({
|
|
queryKey: cloudSearchQueryKeyRoot,
|
|
exact: false,
|
|
});
|
|
// remount the recent card so it clears internal selection (UX nicety)
|
|
setRecentKey((k) => k + 1);
|
|
}}/>
|
|
|
|
{/* New folder modal (reusable) */}
|
|
<NewFolderModal isOpen={isNewFolderOpen} onClose={() => setIsNewFolderOpen(false)} onSubmit={handleCreateFolder}/>
|
|
</div>);
|
|
}
|