mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore(studio): clean up storage UI (#40164)
* copywriting updates * remove unused file and comment * copy tweaks * remove stickySidebarBottom * extraneous comment * remove unused components * unused image * files policies redirect * Update redirects for storage settings * Fix loader for storage policies --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
1 parent
603a4bb583
commit
abd35216f7
17 files changed
+36
-220
No files matched your search
@@ -1,129 +0,0 @@
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { Columns3, Edit2, MoreVertical, Trash, XCircle } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import type { CSSProperties } from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { DeleteBucketModal } from 'components/interfaces/Storage/DeleteBucketModal'
|
||||
import { EditBucketModal } from 'components/interfaces/Storage/EditBucketModal'
|
||||
import { EmptyBucketModal } from 'components/interfaces/Storage/EmptyBucketModal'
|
||||
import type { Bucket } from 'data/storage/buckets-query'
|
||||
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
cn,
|
||||
} from 'ui'
|
||||
|
||||
export interface BucketRowProps {
|
||||
bucket: Bucket
|
||||
projectRef?: string
|
||||
isSelected: boolean
|
||||
style?: CSSProperties
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const BucketRow = ({
|
||||
bucket,
|
||||
projectRef = '',
|
||||
isSelected = false,
|
||||
style,
|
||||
className,
|
||||
}: BucketRowProps) => {
|
||||
const { can: canUpdateBuckets } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
|
||||
const [modal, setModal] = useState<string | null>(null)
|
||||
const onClose = () => setModal(null)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={bucket.id}
|
||||
style={style}
|
||||
className={cn(
|
||||
'group flex items-center justify-between rounded-md',
|
||||
isSelected && 'text-foreground bg-surface-100',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* Even though we trim whitespaces from bucket names, there may be some existing buckets with trailing whitespaces. */}
|
||||
<Link
|
||||
href={`/project/${projectRef}/storage/buckets/${encodeURIComponent(bucket.id)}`}
|
||||
className="py-1 pl-3 pr-1 flex-grow min-w-0"
|
||||
>
|
||||
<div className="flex items-center justify-between space-x-2 truncate w-full">
|
||||
<p
|
||||
className={cn(
|
||||
'text-sm group-hover:text-foreground transition truncate',
|
||||
isSelected ? 'text-foreground' : 'text-foreground-light'
|
||||
)}
|
||||
title={bucket.name}
|
||||
>
|
||||
{bucket.name}
|
||||
</p>
|
||||
{bucket.public && <Badge variant="warning">Public</Badge>}
|
||||
{bucket.type === 'ANALYTICS' && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Columns3 className="text-foreground-lighter" size="20" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Analytics bucket</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
{canUpdateBuckets && isSelected ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button type="text" icon={<MoreVertical />} className="mr-1 h-6 w-6" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="start">
|
||||
{bucket.type !== 'ANALYTICS' && (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
key="toggle-private"
|
||||
className="space-x-2"
|
||||
onClick={() => setModal(`edit`)}
|
||||
>
|
||||
<Edit2 size={14} />
|
||||
<p>Edit bucket</p>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
key="empty-bucket"
|
||||
className="space-x-2"
|
||||
onClick={() => setModal(`empty`)}
|
||||
>
|
||||
<XCircle size={14} />
|
||||
<p>Empty bucket</p>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
key="delete-bucket"
|
||||
className="space-x-2"
|
||||
onClick={() => setModal(`delete`)}
|
||||
>
|
||||
<Trash size={14} />
|
||||
<p>Delete bucket</p>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
<div className="min-w-6 mr-1" />
|
||||
)}
|
||||
|
||||
<EditBucketModal visible={modal === `edit`} bucket={bucket} onClose={onClose} />
|
||||
<EmptyBucketModal visible={modal === `empty`} bucket={bucket} onClose={onClose} />
|
||||
<DeleteBucketModal visible={modal === `delete`} bucket={bucket} onClose={onClose} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Bucket } from 'icons'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { Button } from 'ui'
|
||||
import { BUCKET_TYPES } from './Storage.constants'
|
||||
|
||||
export const BucketsComingSoon = ({ type }: { type: 'analytics' | 'vector' }) => {
|
||||
return (
|
||||
@@ -13,9 +14,9 @@ export const BucketsComingSoon = ({ type }: { type: 'analytics' | 'vector' }) =>
|
||||
<h3>Coming soon</h3>
|
||||
<p className="text-foreground-light text-sm">
|
||||
{type === 'analytics'
|
||||
? 'Store large datasets for analytics and reporting'
|
||||
? BUCKET_TYPES.analytics.valueProp
|
||||
: type === 'vector'
|
||||
? 'Store, index, and query your vector embeddings at scale.'
|
||||
? BUCKET_TYPES.vectors.valueProp
|
||||
: undefined}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,6 @@ export const FilesBuckets = () => {
|
||||
buckets.filter((bucket) => !('type' in bucket) || bucket.type === 'STANDARD').length === 0 ? (
|
||||
<EmptyBucketState bucketType="files" className="mt-12" />
|
||||
) : (
|
||||
// Add !pt-8 to override the default first:pt-12
|
||||
<ScaffoldSection isFullWidth className="h-full gap-y-4">
|
||||
<div className="flex flex-grow justify-between gap-x-2 items-center">
|
||||
<div className="flex items-center gap-x-2">
|
||||
|
||||
@@ -60,14 +60,12 @@ export const CONTEXT_MENU_KEYS = {
|
||||
STORAGE_FOLDER: 'STORAGE_FOLDER',
|
||||
}
|
||||
|
||||
// New bucket types configuration
|
||||
|
||||
export const BUCKET_TYPES = {
|
||||
files: {
|
||||
displayName: 'Files',
|
||||
singularName: 'file',
|
||||
article: 'a',
|
||||
description: 'General file storage for most types of digital content.',
|
||||
description: 'General file storage for most types of digital content',
|
||||
valueProp: 'Store images, videos, documents, and any other file type.',
|
||||
docsUrl: `${DOCS_URL}/guides/storage/buckets/fundamentals`,
|
||||
},
|
||||
@@ -75,7 +73,7 @@ export const BUCKET_TYPES = {
|
||||
displayName: 'Analytics',
|
||||
singularName: 'analytics',
|
||||
article: 'an',
|
||||
description: 'Purpose-built storage for analytical workloads.',
|
||||
description: 'Purpose-built storage for analytical workloads',
|
||||
valueProp: 'Store large datasets for analytics and reporting.',
|
||||
docsUrl: `${DOCS_URL}/guides/storage/analytics/introduction`,
|
||||
},
|
||||
@@ -83,7 +81,7 @@ export const BUCKET_TYPES = {
|
||||
displayName: 'Vectors',
|
||||
singularName: 'vector',
|
||||
article: 'a',
|
||||
description: 'Purpose-built storage for vector data.',
|
||||
description: 'Purpose-built storage for vector data',
|
||||
valueProp: 'Store, index, and query your vector embeddings at scale.',
|
||||
docsUrl: `${DOCS_URL}/guides/storage/vectors`,
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useDatabasePolicyDeleteMutation } from 'data/database-policies/database
|
||||
import { useDatabasePolicyUpdateMutation } from 'data/database-policies/database-policy-update-mutation'
|
||||
import { useBucketsQuery } from 'data/storage/buckets-query'
|
||||
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
|
||||
import { Loader } from 'lucide-react'
|
||||
import { GenericSkeletonLoader } from 'ui-patterns'
|
||||
import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog'
|
||||
import { formatPoliciesForStorage } from '../Storage.utils'
|
||||
import { StoragePoliciesBucketRow } from './StoragePoliciesBucketRow'
|
||||
@@ -182,9 +182,9 @@ export const StoragePolicies = () => {
|
||||
return (
|
||||
<div className="flex min-h-full w-full flex-col">
|
||||
{isLoading ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Loader className="animate-spin" size={16} />
|
||||
</div>
|
||||
<ScaffoldSection isFullWidth>
|
||||
<GenericSkeletonLoader />
|
||||
</ScaffoldSection>
|
||||
) : (
|
||||
<div>
|
||||
<ScaffoldSection isFullWidth>
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
import { CardContent, Card } from 'ui'
|
||||
import { Card, CardContent } from 'ui'
|
||||
|
||||
const StoragePoliciesPlaceholder = () => (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Create a bucket first to start writing policies!
|
||||
<p className="text-sm text-foreground-lighter">
|
||||
Create a bucket first to start writing policies
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -125,7 +125,7 @@ export const S3Connection = () => {
|
||||
<div>
|
||||
<ScaffoldSectionTitle>Connection</ScaffoldSectionTitle>
|
||||
<ScaffoldSectionDescription>
|
||||
Connect to your bucket using any S3-compatible service via the S3 protocol.
|
||||
Connect to your bucket using any S3-compatible service via the S3 protocol
|
||||
</ScaffoldSectionDescription>
|
||||
</div>
|
||||
<DocsButton href={`${DOCS_URL}/guides/storage/s3/authentication`} />
|
||||
@@ -153,7 +153,7 @@ export const S3Connection = () => {
|
||||
<FormItemLayout
|
||||
layout="horizontal"
|
||||
className="[&>*>label]:text-foreground"
|
||||
label="Enable connection via S3 protocol"
|
||||
label="S3 protocol connection"
|
||||
description="Allow clients to connect to Supabase Storage via the S3 protocol"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
@@ -242,7 +242,7 @@ export const S3Connection = () => {
|
||||
<div>
|
||||
<ScaffoldSectionTitle>Access keys</ScaffoldSectionTitle>
|
||||
<ScaffoldSectionDescription>
|
||||
Manage your access keys for this project.
|
||||
Manage your access keys for this project
|
||||
</ScaffoldSectionDescription>
|
||||
</div>
|
||||
<CreateCredentialModal visible={openCreateCred} onOpenChange={setOpenCreateCred} />
|
||||
|
||||
@@ -16,6 +16,7 @@ import UpgradeToPro from 'components/ui/UpgradeToPro'
|
||||
import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query'
|
||||
import { useProjectStorageConfigUpdateUpdateMutation } from 'data/config/project-storage-config-update-mutation'
|
||||
import { useBucketsQuery } from 'data/storage/buckets-query'
|
||||
import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
|
||||
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
|
||||
import { DOCS_URL } from 'lib/constants'
|
||||
@@ -47,12 +48,10 @@ import {
|
||||
} from './StorageListV2MigrationCallout'
|
||||
import {
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_CAPPED,
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_FREE_PLAN,
|
||||
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES_UNCAPPED,
|
||||
StorageSizeUnits,
|
||||
} from './StorageSettings.constants'
|
||||
import { convertFromBytes, convertToBytes } from './StorageSettings.utils'
|
||||
import { useCheckEntitlements } from 'hooks/misc/useCheckEntitlements'
|
||||
|
||||
const formId = 'storage-settings-form'
|
||||
|
||||
@@ -250,7 +249,7 @@ export const StorageSettings = () => {
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Enable Image Transformation"
|
||||
label="Enable image transformation"
|
||||
description={
|
||||
<>
|
||||
Optimize and resize images on the fly.{' '}
|
||||
|
||||
@@ -64,7 +64,6 @@ export interface ProjectLayoutProps {
|
||||
productMenu?: ReactNode
|
||||
selectedTable?: string
|
||||
resizableSidebar?: boolean
|
||||
stickySidebarBottom?: boolean
|
||||
productMenuClassName?: string
|
||||
}
|
||||
|
||||
@@ -79,7 +78,7 @@ export const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<Projec
|
||||
children,
|
||||
selectedTable,
|
||||
resizableSidebar = false,
|
||||
stickySidebarBottom = false,
|
||||
|
||||
productMenuClassName,
|
||||
},
|
||||
ref
|
||||
@@ -221,11 +220,7 @@ export const ProjectLayout = forwardRef<HTMLDivElement, PropsWithChildren<Projec
|
||||
</div>
|
||||
<CreateBranchModal />
|
||||
<ProjectAPIDocs />
|
||||
<MobileSheetNav
|
||||
open={mobileMenuOpen}
|
||||
onOpenChange={setMobileMenuOpen}
|
||||
stickyBottom={stickySidebarBottom}
|
||||
>
|
||||
<MobileSheetNav open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
|
||||
{productMenu}
|
||||
</MobileSheetNav>
|
||||
</>
|
||||
|
||||
@@ -149,6 +149,11 @@ const nextConfig = {
|
||||
destination: '/project/:ref/storage/files',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/project/:ref/storage/policies',
|
||||
destination: '/project/:ref/storage/files/policies',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/project/:ref/storage/buckets/:bucketId',
|
||||
destination: '/project/:ref/storage/files/buckets/:bucketId',
|
||||
@@ -156,7 +161,12 @@ const nextConfig = {
|
||||
},
|
||||
{
|
||||
source: '/project/:ref/settings/storage',
|
||||
destination: '/project/:ref/storage/settings',
|
||||
destination: '/project/:ref/storage/files/settings',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/project/:ref/storage/settings',
|
||||
destination: '/project/:ref/storage/files/settings',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ Hooks.getLayout = (page) => (
|
||||
<AuthLayout>
|
||||
<PageLayout
|
||||
title="Auth Hooks"
|
||||
subtitle="Use Postgres functions or HTTP endpoints to customize the behavior of Supabase Auth to meet your needs"
|
||||
subtitle="Use Postgres functions or HTTP endpoints to customize the behavior of Supabase Auth"
|
||||
secondaryActions={secondaryActions}
|
||||
>
|
||||
{page}
|
||||
|
||||
@@ -18,7 +18,7 @@ ProvidersPage.getLayout = (page) => (
|
||||
<AuthLayout>
|
||||
<PageLayout
|
||||
title="OAuth Server"
|
||||
subtitle="Configure your project to act as an identity provider for third-party applications."
|
||||
subtitle="Configure your project to act as an identity provider for third-party applications"
|
||||
>
|
||||
{page}
|
||||
</PageLayout>
|
||||
|
||||
@@ -17,7 +17,7 @@ const DatabaseEnumeratedTypes: NextPageWithLayout = () => {
|
||||
<FormHeader
|
||||
className="!mb-0"
|
||||
title="Database Enumerated Types"
|
||||
description="Custom data types that you can use in your database tables or functions."
|
||||
description="Custom data types that you can use in your database tables or functions"
|
||||
/>
|
||||
</ScaffoldSectionContent>
|
||||
<div className="col-span-12 mt-3">
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { StoragePolicies } from 'components/interfaces/Storage/StoragePolicies/StoragePolicies'
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import { PageLayout } from 'components/layouts/PageLayout/PageLayout'
|
||||
import { ScaffoldContainer } from 'components/layouts/Scaffold'
|
||||
import StorageLayout from 'components/layouts/StorageLayout/StorageLayout'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
const StoragePoliciesPage: NextPageWithLayout = () => {
|
||||
return <StoragePolicies />
|
||||
}
|
||||
|
||||
StoragePoliciesPage.getLayout = (page) => (
|
||||
<DefaultLayout>
|
||||
<StorageLayout title="Policies">
|
||||
<PageLayout
|
||||
title="Storage policies"
|
||||
subtitle="Safeguard your files with policies that define the operations allowed for your users at the
|
||||
bucket level."
|
||||
>
|
||||
<ScaffoldContainer>{page}</ScaffoldContainer>
|
||||
</PageLayout>
|
||||
</StorageLayout>
|
||||
</DefaultLayout>
|
||||
)
|
||||
|
||||
export default StoragePoliciesPage
|
||||
@@ -1,27 +0,0 @@
|
||||
import { S3Connection } from 'components/interfaces/Storage/StorageSettings/S3Connection'
|
||||
import { StorageSettings } from 'components/interfaces/Storage/StorageSettings/StorageSettings'
|
||||
import DefaultLayout from 'components/layouts/DefaultLayout'
|
||||
import { PageLayout } from 'components/layouts/PageLayout/PageLayout'
|
||||
import { ScaffoldContainer } from 'components/layouts/Scaffold'
|
||||
import StorageLayout from 'components/layouts/StorageLayout/StorageLayout'
|
||||
import type { NextPageWithLayout } from 'types'
|
||||
|
||||
const StorageSettingsPage: NextPageWithLayout = () => {
|
||||
return (
|
||||
<>
|
||||
<StorageSettings />
|
||||
<S3Connection />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
StorageSettingsPage.getLayout = (page) => (
|
||||
<DefaultLayout>
|
||||
<StorageLayout title="Settings">
|
||||
<PageLayout title="Storage settings" subtitle="Configure your project's storage settings">
|
||||
<ScaffoldContainer>{page}</ScaffoldContainer>
|
||||
</PageLayout>
|
||||
</StorageLayout>
|
||||
</DefaultLayout>
|
||||
)
|
||||
export default StorageSettingsPage
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB |
@@ -11,8 +11,7 @@ const MobileSheetNav: React.FC<{
|
||||
children: React.ReactNode
|
||||
open?: boolean
|
||||
onOpenChange(open: boolean): void
|
||||
stickyBottom?: boolean
|
||||
}> = ({ children, open = false, onOpenChange, stickyBottom = false }) => {
|
||||
}> = ({ children, open = false, onOpenChange }) => {
|
||||
const router = useRouter()
|
||||
const { width } = useWindowSize()
|
||||
|
||||
@@ -32,10 +31,7 @@ const MobileSheetNav: React.FC<{
|
||||
showClose={false}
|
||||
size="full"
|
||||
side="bottom"
|
||||
className={cn(
|
||||
'rounded-t-lg overflow-hidden overflow-y-scroll h-[85dvh] md:max-h-[500px]',
|
||||
stickyBottom ? 'pt-2 pb-0' : 'py-2'
|
||||
)}
|
||||
className={cn('rounded-t-lg overflow-hidden overflow-y-scroll h-[85dvh] md:max-h-[500px]')}
|
||||
>
|
||||
<ErrorBoundary FallbackComponent={() => <CommandEmpty_Shadcn_ />}>{children}</ErrorBoundary>
|
||||
</SheetContent>
|
||||
|
||||
Reference in new issue
Block a user