swap buckets and bucketsV2: make the "show deleted files" view the default

The bucket page at /storage/files/buckets/[bucketId] now shows the
"Show deleted files" switch experience (previously only at bucketsV2).
The Files/Deleted files tabbed view moves to /storage/files/bucketsV2
instead. Route files are untouched since they import by page path, not
by component name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TiUEvmC84bRsteqsWXHY2p
This commit is contained in:
Claude committed 2026-08-05 15:48:11 +00:00
1 parent 889ab611a6
commit bc3bbb6f13
2 files changed
+304 -304

No files matched your search

@@ -2,7 +2,7 @@ import { useParams } from 'common'
import { ChevronDown, FolderOpen, Settings, Shield, Trash2 } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { parseAsBoolean, parseAsStringEnum, useQueryState } from 'nuqs'
import { parseAsBoolean, useQueryState } from 'nuqs'
import { useEffect } from 'react'
import { toast } from 'sonner'
import {
@@ -18,8 +18,8 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
NavMenu,
NavMenuItem,
Label,
Switch,
Tooltip,
TooltipContent,
TooltipTrigger,
@@ -34,24 +34,36 @@ import { useSelectedBucket } from '@/components/interfaces/Storage/FilesBuckets/
import { PublicBucketWarning } from '@/components/interfaces/Storage/PublicBucketWarning'
import { PUBLIC_BUCKET_TOOLTIP } from '@/components/interfaces/Storage/Storage.constants'
import StorageBucketsError from '@/components/interfaces/Storage/StorageBucketsError'
import { DeletedFilesProvider } from '@/components/interfaces/Storage/StorageExplorer/DeletedFilesContext'
import {
DeletedFilesProvider,
useDeletedFilesContext,
} from '@/components/interfaces/Storage/StorageExplorer/DeletedFilesContext'
import { StorageExplorer } from '@/components/interfaces/Storage/StorageExplorer/StorageExplorer'
import { useIsStorageProtectionEnabled } from '@/components/interfaces/Storage/StorageProtection.constants'
import { Trash } from '@/components/interfaces/Storage/Trash/Trash'
import { useBucketPolicyCount } from '@/components/interfaces/Storage/useBucketPolicyCount'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
import StorageLayout from '@/components/layouts/StorageLayout/StorageLayout'
import { StorageExplorerStateContextProvider } from '@/state/storage-explorer'
import type { NextPageWithLayout } from '@/types'
type BucketView = 'files' | 'trash'
const BUCKET_VIEWS: BucketView[] = ['files', 'trash']
const BucketPage: NextPageWithLayout = () => {
const { ref } = useParams()
return (
<StorageExplorerStateContextProvider key={`storage-explorer-state-${ref}`}>
<DeletedFilesProvider enabled>
<BucketPageInner />
</DeletedFilesProvider>
</StorageExplorerStateContextProvider>
)
}
const BucketPageInner = () => {
const router = useRouter()
const { bucketId, ref } = useParams()
const { data: bucket, error, isSuccess, isError } = useSelectedBucket()
const { isShowingDeleted, setIsShowingDeleted } = useDeletedFilesContext()
const [showEditModal, setShowEditModal] = useQueryState(
'edit',
parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
@@ -65,16 +77,8 @@ const BucketPage: NextPageWithLayout = () => {
parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
)
const [view, setView] = useQueryState(
'view',
parseAsStringEnum<BucketView>(BUCKET_VIEWS)
.withDefault('files')
.withOptions({ history: 'push', clearOnDefault: true })
)
const { getPolicyCount } = useBucketPolicyCount()
const policyCount = bucket ? getPolicyCount(bucket.id) : 0
const showProtection = useIsStorageProtectionEnabled()
useEffect(() => {
if (isSuccess && !bucket) {
@@ -89,147 +93,138 @@ const BucketPage: NextPageWithLayout = () => {
}
return (
<StorageExplorerStateContextProvider key={`storage-explorer-state-${ref}`}>
<DeletedFilesProvider enabled={false}>
<div className="w-full min-h-full flex flex-col items-stretch">
<PageBreadcrumbs
actions={
<PageBreadcrumbsActions>
<Button
asChild
variant="outline"
size="tiny"
icon={<Shield size={14} />}
iconRight={
policyCount > 0 ? (
<span className="w-4 h-4 bg-surface-200 text-foreground-light text-xs rounded-full flex items-center justify-center font-medium">
{policyCount}
</span>
) : undefined
}
>
<Link
href={`/project/${ref}/storage/files/policies?search=${encodeURIComponent(bucket?.name ?? '')}`}
>
Policies
</Link>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="tiny" iconRight={<ChevronDown size={14} />}>
Edit bucket
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowEditModal(true)}
>
<Settings size={12} />
<p>Bucket settings</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowEmptyModal(true)}
>
<FolderOpen size={12} />
<p>Empty bucket</p>
</DropdownMenuItem>
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowDeleteModal(true)}
>
<Trash2 size={12} />
<p>Delete bucket</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</PageBreadcrumbsActions>
}
>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/project/${ref}/storage/files`}>Files</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/project/${ref}/storage/files`}>Buckets</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="flex min-w-0 items-center gap-2">
<span className="truncate">{bucketId}</span>
{bucket?.public && (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="warning" className="flex shrink-0">
Public
</Badge>
</TooltipTrigger>
<TooltipContent side="bottom">{PUBLIC_BUCKET_TOOLTIP}</TooltipContent>
</Tooltip>
)}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</PageBreadcrumbs>
{showProtection && (
<NavMenu aria-label="Bucket views" className="px-4">
<NavMenuItem active={view === 'files'}>
<button type="button" onClick={() => setView('files')}>
Files
</button>
</NavMenuItem>
<NavMenuItem active={view === 'trash'}>
<button type="button" onClick={() => setView('trash')}>
Deleted files
</button>
</NavMenuItem>
</NavMenu>
)}
{view === 'files' && (
<PageContainer size="full" className="flex flex-1 min-h-0 flex-col px-0 xl:px-0">
{ref && bucketId && (
<div className="px-4 py-4 empty:hidden">
<PublicBucketWarning projectRef={ref} bucketId={bucketId} />
</div>
)}
<div className="flex-1 min-h-0">
<StorageExplorer />
<>
<div className="w-full min-h-full flex flex-col items-stretch">
<PageBreadcrumbs
actions={
<PageBreadcrumbsActions>
<div className="flex items-center gap-2">
<Switch
id="show-deleted"
size="small"
checked={isShowingDeleted}
onCheckedChange={setIsShowingDeleted}
/>
<Label htmlFor="show-deleted" className="text-xs cursor-pointer">
Show deleted files
</Label>
</div>
</PageContainer>
)}
{view === 'trash' && bucket && <Trash bucketId={bucket.name} />}
</div>
<Button
asChild
variant="outline"
size="tiny"
icon={<Shield size={14} />}
iconRight={
policyCount > 0 ? (
<span className="w-4 h-4 bg-surface-200 text-foreground-light text-xs rounded-full flex items-center justify-center font-medium">
{policyCount}
</span>
) : undefined
}
>
<Link
href={`/project/${ref}/storage/files/policies?search=${encodeURIComponent(bucket?.name ?? '')}`}
>
Policies
</Link>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="tiny" iconRight={<ChevronDown size={14} />}>
Edit bucket
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowEditModal(true)}
>
<Settings size={12} />
<p>Bucket settings</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowEmptyModal(true)}
>
<FolderOpen size={12} />
<p>Empty bucket</p>
</DropdownMenuItem>
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowDeleteModal(true)}
>
<Trash2 size={12} />
<p>Delete bucket</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</PageBreadcrumbsActions>
}
>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/project/${ref}/storage/files`}>Files</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/project/${ref}/storage/files`}>Buckets</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="flex min-w-0 items-center gap-2">
<span className="truncate">{bucketId}</span>
{bucket?.public && (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="warning" className="flex shrink-0">
Public
</Badge>
</TooltipTrigger>
<TooltipContent side="bottom">{PUBLIC_BUCKET_TOOLTIP}</TooltipContent>
</Tooltip>
)}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</PageBreadcrumbs>
{bucket && (
<>
<EditBucketModal
visible={showEditModal}
bucket={bucket}
onClose={() => setShowEditModal(false)}
/>
<EmptyBucketModal
visible={showEmptyModal}
bucket={bucket}
onClose={() => setShowEmptyModal(false)}
/>
<DeleteBucketModal
visible={showDeleteModal}
bucket={bucket}
onClose={() => setShowDeleteModal(false)}
/>
</>
)}
</DeletedFilesProvider>
</StorageExplorerStateContextProvider>
<PageContainer size="full" className="flex flex-1 min-h-0 flex-col px-0 xl:px-0">
{ref && bucketId && (
<div className="px-4 py-4 empty:hidden">
<PublicBucketWarning projectRef={ref} bucketId={bucketId} />
</div>
)}
<div className="flex-1 min-h-0">
<StorageExplorer />
</div>
</PageContainer>
</div>
{bucket && (
<>
<EditBucketModal
visible={showEditModal}
bucket={bucket}
onClose={() => setShowEditModal(false)}
/>
<EmptyBucketModal
visible={showEmptyModal}
bucket={bucket}
onClose={() => setShowEmptyModal(false)}
/>
<DeleteBucketModal
visible={showDeleteModal}
bucket={bucket}
onClose={() => setShowDeleteModal(false)}
/>
</>
)}
</>
)
}
@@ -2,7 +2,7 @@ import { useParams } from 'common'
import { ChevronDown, FolderOpen, Settings, Shield, Trash2 } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { parseAsBoolean, useQueryState } from 'nuqs'
import { parseAsBoolean, parseAsStringEnum, useQueryState } from 'nuqs'
import { useEffect } from 'react'
import { toast } from 'sonner'
import {
@@ -18,8 +18,8 @@ import {
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Label,
Switch,
NavMenu,
NavMenuItem,
Tooltip,
TooltipContent,
TooltipTrigger,
@@ -34,36 +34,24 @@ import { useSelectedBucket } from '@/components/interfaces/Storage/FilesBuckets/
import { PublicBucketWarning } from '@/components/interfaces/Storage/PublicBucketWarning'
import { PUBLIC_BUCKET_TOOLTIP } from '@/components/interfaces/Storage/Storage.constants'
import StorageBucketsError from '@/components/interfaces/Storage/StorageBucketsError'
import {
DeletedFilesProvider,
useDeletedFilesContext,
} from '@/components/interfaces/Storage/StorageExplorer/DeletedFilesContext'
import { DeletedFilesProvider } from '@/components/interfaces/Storage/StorageExplorer/DeletedFilesContext'
import { StorageExplorer } from '@/components/interfaces/Storage/StorageExplorer/StorageExplorer'
import { useIsStorageProtectionEnabled } from '@/components/interfaces/Storage/StorageProtection.constants'
import { Trash } from '@/components/interfaces/Storage/Trash/Trash'
import { useBucketPolicyCount } from '@/components/interfaces/Storage/useBucketPolicyCount'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
import StorageLayout from '@/components/layouts/StorageLayout/StorageLayout'
import { StorageExplorerStateContextProvider } from '@/state/storage-explorer'
import type { NextPageWithLayout } from '@/types'
type BucketView = 'files' | 'trash'
const BUCKET_VIEWS: BucketView[] = ['files', 'trash']
const BucketV2Page: NextPageWithLayout = () => {
const { ref } = useParams()
return (
<StorageExplorerStateContextProvider key={`storage-explorer-state-${ref}`}>
<DeletedFilesProvider enabled>
<BucketV2PageInner />
</DeletedFilesProvider>
</StorageExplorerStateContextProvider>
)
}
const BucketV2PageInner = () => {
const router = useRouter()
const { bucketId, ref } = useParams()
const { data: bucket, error, isSuccess, isError } = useSelectedBucket()
const { isShowingDeleted, setIsShowingDeleted } = useDeletedFilesContext()
const [showEditModal, setShowEditModal] = useQueryState(
'edit',
parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
@@ -77,8 +65,16 @@ const BucketV2PageInner = () => {
parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
)
const [view, setView] = useQueryState(
'view',
parseAsStringEnum<BucketView>(BUCKET_VIEWS)
.withDefault('files')
.withOptions({ history: 'push', clearOnDefault: true })
)
const { getPolicyCount } = useBucketPolicyCount()
const policyCount = bucket ? getPolicyCount(bucket.id) : 0
const showProtection = useIsStorageProtectionEnabled()
useEffect(() => {
if (isSuccess && !bucket) {
@@ -93,138 +89,147 @@ const BucketV2PageInner = () => {
}
return (
<>
<div className="w-full min-h-full flex flex-col items-stretch">
<PageBreadcrumbs
actions={
<PageBreadcrumbsActions>
<div className="flex items-center gap-2">
<Switch
id="show-deleted"
size="small"
checked={isShowingDeleted}
onCheckedChange={setIsShowingDeleted}
/>
<Label htmlFor="show-deleted" className="text-xs cursor-pointer">
Show deleted files
</Label>
</div>
<Button
asChild
variant="outline"
size="tiny"
icon={<Shield size={14} />}
iconRight={
policyCount > 0 ? (
<span className="w-4 h-4 bg-surface-200 text-foreground-light text-xs rounded-full flex items-center justify-center font-medium">
{policyCount}
</span>
) : undefined
}
>
<Link
href={`/project/${ref}/storage/files/policies?search=${encodeURIComponent(bucket?.name ?? '')}`}
<StorageExplorerStateContextProvider key={`storage-explorer-state-${ref}`}>
<DeletedFilesProvider enabled={false}>
<div className="w-full min-h-full flex flex-col items-stretch">
<PageBreadcrumbs
actions={
<PageBreadcrumbsActions>
<Button
asChild
variant="outline"
size="tiny"
icon={<Shield size={14} />}
iconRight={
policyCount > 0 ? (
<span className="w-4 h-4 bg-surface-200 text-foreground-light text-xs rounded-full flex items-center justify-center font-medium">
{policyCount}
</span>
) : undefined
}
>
Policies
</Link>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="tiny" iconRight={<ChevronDown size={14} />}>
Edit bucket
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowEditModal(true)}
<Link
href={`/project/${ref}/storage/files/policies?search=${encodeURIComponent(bucket?.name ?? '')}`}
>
<Settings size={12} />
<p>Bucket settings</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowEmptyModal(true)}
>
<FolderOpen size={12} />
<p>Empty bucket</p>
</DropdownMenuItem>
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowDeleteModal(true)}
>
<Trash2 size={12} />
<p>Delete bucket</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</PageBreadcrumbsActions>
}
>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/project/${ref}/storage/files`}>Files</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/project/${ref}/storage/files`}>Buckets</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="flex min-w-0 items-center gap-2">
<span className="truncate">{bucketId}</span>
{bucket?.public && (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="warning" className="flex shrink-0">
Public
</Badge>
</TooltipTrigger>
<TooltipContent side="bottom">{PUBLIC_BUCKET_TOOLTIP}</TooltipContent>
</Tooltip>
)}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</PageBreadcrumbs>
Policies
</Link>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="tiny" iconRight={<ChevronDown size={14} />}>
Edit bucket
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-40">
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowEditModal(true)}
>
<Settings size={12} />
<p>Bucket settings</p>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowEmptyModal(true)}
>
<FolderOpen size={12} />
<p>Empty bucket</p>
</DropdownMenuItem>
<DropdownMenuItem
className="flex items-center space-x-2"
onClick={() => setShowDeleteModal(true)}
>
<Trash2 size={12} />
<p>Delete bucket</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</PageBreadcrumbsActions>
}
>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/project/${ref}/storage/files`}>Files</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={`/project/${ref}/storage/files`}>Buckets</Link>
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="flex min-w-0 items-center gap-2">
<span className="truncate">{bucketId}</span>
{bucket?.public && (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="warning" className="flex shrink-0">
Public
</Badge>
</TooltipTrigger>
<TooltipContent side="bottom">{PUBLIC_BUCKET_TOOLTIP}</TooltipContent>
</Tooltip>
)}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</PageBreadcrumbs>
<PageContainer size="full" className="flex flex-1 min-h-0 flex-col px-0 xl:px-0">
{ref && bucketId && (
<div className="px-4 py-4 empty:hidden">
<PublicBucketWarning projectRef={ref} bucketId={bucketId} />
</div>
{showProtection && (
<NavMenu aria-label="Bucket views" className="px-4">
<NavMenuItem active={view === 'files'}>
<button type="button" onClick={() => setView('files')}>
Files
</button>
</NavMenuItem>
<NavMenuItem active={view === 'trash'}>
<button type="button" onClick={() => setView('trash')}>
Deleted files
</button>
</NavMenuItem>
</NavMenu>
)}
<div className="flex-1 min-h-0">
<StorageExplorer />
</div>
</PageContainer>
</div>
{bucket && (
<>
<EditBucketModal
visible={showEditModal}
bucket={bucket}
onClose={() => setShowEditModal(false)}
/>
<EmptyBucketModal
visible={showEmptyModal}
bucket={bucket}
onClose={() => setShowEmptyModal(false)}
/>
<DeleteBucketModal
visible={showDeleteModal}
bucket={bucket}
onClose={() => setShowDeleteModal(false)}
/>
</>
)}
</>
{view === 'files' && (
<PageContainer size="full" className="flex flex-1 min-h-0 flex-col px-0 xl:px-0">
{ref && bucketId && (
<div className="px-4 py-4 empty:hidden">
<PublicBucketWarning projectRef={ref} bucketId={bucketId} />
</div>
)}
<div className="flex-1 min-h-0">
<StorageExplorer />
</div>
</PageContainer>
)}
{view === 'trash' && bucket && <Trash bucketId={bucket.name} />}
</div>
{bucket && (
<>
<EditBucketModal
visible={showEditModal}
bucket={bucket}
onClose={() => setShowEditModal(false)}
/>
<EmptyBucketModal
visible={showEmptyModal}
bucket={bucket}
onClose={() => setShowEmptyModal(false)}
/>
<DeleteBucketModal
visible={showDeleteModal}
bucket={bucket}
onClose={() => setShowDeleteModal(false)}
/>
</>
)}
</DeletedFilesProvider>
</StorageExplorerStateContextProvider>
)
}