import { keepPreviousData } from '@tanstack/react-query' import { useDebounce, useIntersectionObserver } from '@uidotdev/usehooks' import { useEffect, useMemo, useRef, useState } from 'react' import { cn, Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from 'ui' import type { ResourcePickerRenderProps } from './SecondLevelNav.Layout' import { usePaginatedBucketsQuery } from '@/data/storage/buckets-query' type StorageResourceListProps = ResourcePickerRenderProps & { projectRef?: string } const SEARCH_DEBOUNCE_MS = 400 const useSearchQuery = () => { const [search, setSearch] = useState('') const debouncedSearch = useDebounce(search, SEARCH_DEBOUNCE_MS) const searchQuery = search.length === 0 ? undefined : debouncedSearch return { rawQuery: search, query: searchQuery, setSearch, } } type UseInfiniteLoadingBucketsParams = { projectRef?: string searchQuery?: string rawQuery: string } const useInfiniteLoadingBuckets = ({ projectRef, searchQuery, rawQuery, }: UseInfiniteLoadingBucketsParams) => { const { data, isFetching, hasNextPage, fetchNextPage, isFetchingNextPage } = usePaginatedBucketsQuery( { projectRef, search: searchQuery }, { enabled: !!projectRef, placeholderData: rawQuery.length === 0 ? keepPreviousData : undefined, } ) const buckets = useMemo(() => data?.pages.flatMap((page) => page) ?? [], [data]) const scrollContainerRef = useRef(null) const [sentinelRef, entry] = useIntersectionObserver({ threshold: 1, root: scrollContainerRef.current, rootMargin: '0px', }) useEffect(() => { if (entry?.isIntersecting && hasNextPage && !isFetching) { fetchNextPage() } }, [entry?.isIntersecting, fetchNextPage, hasNextPage, isFetching]) return { isFetching, hasNextPage, isFetchingNextPage, buckets, scrollContainerRef, sentinelRef, } } export const StorageResourceList = ({ projectRef, selectedResource, onSelect, closePopover, }: StorageResourceListProps) => { const { rawQuery, query: searchQuery, setSearch } = useSearchQuery() const { isFetching, hasNextPage, isFetchingNextPage, buckets, scrollContainerRef, sentinelRef } = useInfiniteLoadingBuckets({ projectRef, searchQuery, rawQuery, }) const handleSelect = (value: string) => { onSelect(value) closePopover() } const showEmptyState = !isFetching && buckets.length === 0 const emptyMessage = rawQuery.length > 0 ? 'No buckets found for this search' : 'No buckets available' return ( setSearch('')} /> {isFetching && buckets.length === 0 ? (
Loading buckets...
) : (
{buckets.map((bucket) => { const isActive = bucket.name === selectedResource return ( handleSelect(bucket.name)} >

{bucket.name}

) })} {hasNextPage &&
}
)} {isFetchingNextPage && (
Loading more buckets...
)} ) }