import { useState, useEffect } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import { useSearchParams } from 'react-router-dom'; import { Header } from '@/components/layout/header'; import { useBuckets, useCreateBucket, useDeleteBucket, useGrantBucketPermission } from '@/hooks/useApi'; import { useBucketObjects } from '@/hooks/useBucketObjects'; import { BucketListView } from '@/components/buckets/BucketListView'; import { ObjectBrowserView } from '@/components/buckets/ObjectBrowserView'; import { CreateBucketDialog } from '@/components/buckets/CreateBucketDialog'; import { DeleteBucketDialog } from '@/components/buckets/DeleteBucketDialog'; import { BucketSettingsDialog } from '@/components/buckets/BucketSettingsDialog'; import { BucketWebsiteDialog } from '@/components/buckets/BucketWebsiteDialog'; import type { Bucket } from '@/types'; import { toast } from 'sonner'; import { bucketsApi } from '@/lib/api'; export function Buckets() { const [searchParams, setSearchParams] = useSearchParams(); // Bucket state const [searchQuery, setSearchQuery] = useState(''); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [deleteBucketDialogOpen, setDeleteBucketDialogOpen] = useState(false); const [selectedBucket, setSelectedBucket] = useState(null); const [settingsDialogOpen, setSettingsDialogOpen] = useState(false); const [settingsBucket, setSettingsBucket] = useState(null); const [websiteDialogOpen, setWebsiteDialogOpen] = useState(false); const [websiteBucket, setWebsiteBucket] = useState(null); // Object browser state - initialize from URL params const [viewingBucket, setViewingBucket] = useState(searchParams.get('bucket')); const [currentPath, setCurrentPath] = useState(searchParams.get('prefix') || ''); const [objectSearchQuery, setObjectSearchQuery] = useState(''); const [initialPageToken, setInitialPageToken] = useState( searchParams.get('page') || undefined ); const [initialItemsPerPage, setInitialItemsPerPage] = useState( parseInt(searchParams.get('limit') || '25', 10) ); // Sync URL params with state on mount and when URL changes useEffect(() => { const bucketParam = searchParams.get('bucket'); const prefixParam = searchParams.get('prefix') || ''; const pageParam = searchParams.get('page') || undefined; const limitParam = parseInt(searchParams.get('limit') || '25', 10); if (bucketParam !== viewingBucket) { setViewingBucket(bucketParam); } if (prefixParam !== currentPath) { setCurrentPath(prefixParam); } setInitialPageToken(pageParam); setInitialItemsPerPage(limitParam); }, [searchParams]); // Custom hooks const queryClient = useQueryClient(); const { data: buckets = [], isLoading: bucketsLoading } = useBuckets(); const createBucketMutation = useCreateBucket(); const deleteBucketMutation = useDeleteBucket(); const grantPermissionMutation = useGrantBucketPermission(); const { objects, isLoading: objectsLoading, isRefreshing, isNavigating, isTruncated, nextContinuationToken, itemsPerPage, setItemsPerPage, uploadFiles, uploadTasks, deleteObject, deleteMultipleObjects, createDirectory, fetchObjects } = useBucketObjects( viewingBucket, currentPath ); const handleViewBucket = (bucketName: string) => { setViewingBucket(bucketName); setCurrentPath(''); setObjectSearchQuery(''); setSearchParams({ bucket: bucketName }); }; const handleBackToBuckets = () => { setViewingBucket(null); setCurrentPath(''); setObjectSearchQuery(''); setSearchParams({}); }; const handleNavigateToFolder = (path: string) => { setCurrentPath(path); if (viewingBucket) { const params: Record = { bucket: viewingBucket }; if (path) { params.prefix = path; } // Reset pagination when navigating to a new folder setSearchParams(params); } }; const handlePageChange = (token?: string) => { fetchObjects(token); // Update URL with page token if (viewingBucket) { const params: Record = { bucket: viewingBucket }; if (currentPath) { params.prefix = currentPath; } if (token) { params.page = token; } if (itemsPerPage !== 25) { params.limit = itemsPerPage.toString(); } setSearchParams(params); } }; const handleItemsPerPageChange = (count: number) => { setItemsPerPage(count); // Update URL with new limit if (viewingBucket) { const params: Record = { bucket: viewingBucket }; if (currentPath) { params.prefix = currentPath; } if (count !== 25) { params.limit = count.toString(); } setSearchParams(params); } }; const handleOpenSettings = (bucket: Bucket) => { setSettingsBucket(bucket); setSettingsDialogOpen(true); }; const handleOpenWebsiteSettings = (bucket: Bucket) => { setWebsiteBucket(bucket); setWebsiteDialogOpen(true); }; const handleSaveWebsite = async ( bucketName: string, payload: { enabled: boolean; indexDocument?: string; errorDocument?: string } ): Promise => { try { await bucketsApi.updateBucketWebsite(bucketName, payload); queryClient.invalidateQueries({ queryKey: ['buckets'] }); toast.success('Website configuration updated'); return true; } catch { return false; } }; const handleRefreshObjects = async () => { if (isRefreshing) return; try { await fetchObjects(undefined, true); toast.success('Objects refreshed successfully'); } catch (error) { console.error('Refresh error:', error); } }; // Wrapper functions for mutations to match dialog APIs const createBucket = async (name: string, region?: string) => { try { await createBucketMutation.mutateAsync({ name, region }); return true; } catch (error) { return false; } }; const deleteBucket = async (name: string) => { try { await deleteBucketMutation.mutateAsync(name); return true; } catch (error) { return false; } }; const grantPermission = async ( bucketName: string, accessKeyId: string, permissions: { read: boolean; write: boolean; owner: boolean } ) => { try { await grantPermissionMutation.mutateAsync({ bucketName, accessKeyId, permissions }); return true; } catch (error) { return false; } }; // If viewing a bucket's objects, show the object browser view if (viewingBucket) { return ( ); } // Default view: show buckets list return (
setCreateDialogOpen(true)} onDeleteBucket={(bucket) => { setSelectedBucket(bucket); setDeleteBucketDialogOpen(true); }} />
{/* Dialogs */}
); }