From 83bdc227231bfaf414f85df06f003dd2ad197b44 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:50:56 +1000 Subject: [PATCH 01/12] chore(design-system): empty state documentation (#39890) * suppress webpack/Turbopack warning * empty states first draft * todo note * empty state documentation * remove empty code block and confusing examples --- apps/design-system/README.md | 10 ++--- apps/design-system/__registry__/index.tsx | 33 ++++++++++++++++ apps/design-system/config/docs.ts | 27 +++---------- .../content/docs/ui-patterns/empty-states.mdx | 38 +++++++++++++++++++ apps/design-system/next.config.mjs | 25 ++++++++---- .../example/empty-state-initial-state.tsx | 22 +++++++++++ .../example/empty-state-missing-route.tsx | 22 +++++++++++ .../example/empty-state-zero-items-table.tsx | 25 ++++++++++++ apps/design-system/registry/examples.ts | 15 ++++++++ 9 files changed, 183 insertions(+), 34 deletions(-) create mode 100644 apps/design-system/content/docs/ui-patterns/empty-states.mdx create mode 100644 apps/design-system/registry/default/example/empty-state-initial-state.tsx create mode 100644 apps/design-system/registry/default/example/empty-state-missing-route.tsx create mode 100644 apps/design-system/registry/default/example/empty-state-zero-items-table.tsx diff --git a/apps/design-system/README.md b/apps/design-system/README.md index 127060277616e..959e363180250 100644 --- a/apps/design-system/README.md +++ b/apps/design-system/README.md @@ -58,13 +58,13 @@ The design system _references_ components rather than housing them. That’s an With that out of the way, there are several parts of this design system that need to be manually updated after components have been added or removed (from documentation). These include: - `config/docs.ts`: list of components in the sidebar -- `content/docs`: where the actual component documentation `.mdx` file lives +- `content/docs`: the actual component documentation - `registry/examples.ts`: list of example components -- `registry/default/example`: where the actual example component(s) live -- `registry/charts.ts`: Chart components -- `registry/fragments.ts`: Fragment components +- `registry/default/example`: the actual example components +- `registry/charts.ts`: chart components +- `registry/fragments.ts`: fragment components -You will need to rebuild the design system’s registry each time a new example component is added. In other words: whenever a new file enters `registry`, it needs to be rebuilt. You can do that via: +You may need to rebuild the design system’s registry. You can do that via: ```bash cd apps/design-system diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index 005d827880186..cead6b766f8e2 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -2293,6 +2293,39 @@ export const Index: Record = { subcategory: "undefined", chunks: [] }, + "empty-state-missing-route": { + name: "empty-state-missing-route", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/empty-state-missing-route")), + source: "", + files: ["registry/default/example/empty-state-missing-route.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "empty-state-zero-items-table": { + name: "empty-state-zero-items-table", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/empty-state-zero-items-table")), + source: "", + files: ["registry/default/example/empty-state-zero-items-table.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, + "empty-state-initial-state": { + name: "empty-state-initial-state", + type: "components:example", + registryDependencies: undefined, + component: React.lazy(() => import("@/registry/default/example/empty-state-initial-state")), + source: "", + files: ["registry/default/example/empty-state-initial-state.tsx"], + category: "undefined", + subcategory: "undefined", + chunks: [] + }, "chart-area-axes": { name: "chart-area-axes", type: "components:block", diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index 35407e0b4333c..ec78bcd99caff 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -6,28 +6,6 @@ interface DocsConfig { } export const docsConfig: DocsConfig = { - // mainNav: [ - // { - // title: 'Documentation', - // href: '/docs', - // }, - // { - // title: 'Components', - // href: '/docs/components/accordion', - // }, - // { - // title: 'Themes', - // href: '/themes', - // }, - // { - // title: 'Examples', - // href: '/examples', - // }, - // { - // title: 'Blocks', - // href: '/blocks', - // }, - // ], sidebarNav: [ { title: 'Getting Started', @@ -77,6 +55,11 @@ export const docsConfig: DocsConfig = { href: '/docs/ui-patterns/navigation', items: [], }, + { + title: 'Empty States', + href: '/docs/ui-patterns/empty-states', + items: [], + }, ], }, { diff --git a/apps/design-system/content/docs/ui-patterns/empty-states.mdx b/apps/design-system/content/docs/ui-patterns/empty-states.mdx new file mode 100644 index 0000000000000..876f5020a0eee --- /dev/null +++ b/apps/design-system/content/docs/ui-patterns/empty-states.mdx @@ -0,0 +1,38 @@ +--- +title: Empty states +description: Convey the absence of data and provide clear instruction for what to do about it. +--- + +At a minimum, empty states convey the fact that there is nothing to list, perform, or display on the current page. They should also provide a clear call to action for the user to take. + +## Missing route + +Users may accidentally navigate to a non-existent dynamic route, such as a non-existent bucket in [Storage](https://supabase.com/dashboard/project/_/storage) or a non-existent table in the [Table Editor](https://supabase.com/dashboard/project/_/editor). In these cases, follow the pattern of a centered [Admonition](../fragments/admonition) as shown below.. + + + +## Zero results + +Tabular information without results—or perhaps no data to begin with—should have an empty state that matches the larger presentation. + +For instance, a [Table](../components/table) may just display a single row just like it would if it had data. Dulling the TableHead text color and removing the TableCell hover state can further reinforce the lack of usable data. + + + +The treatment for other layouts, such as the list of users in [Authentication](https://supabase.com/dashboard/project/_/auth/users), should match their own general styling. + +## Initial state + +Perhaps the user has not yet created any data yet. They might be a feature for the first time. In these cases, the empty state should provide the briefest information about the lack of data, putting more focus on the value proposition and primary action. + + + +Keep in mind that this empty state will likely appear after a visual loading state. Consider layout shift and button placement during and after the transition. + +## Components + +There is not yet a shared empty state UI component. The context and needs for each placement differ enough to warrant custom components for each placement. That said, we should aim to make these as consistent as possible over time. See the below examples that might share common logic in a future centralized component. + +## External references + +- [_Empty States_ on GitHub Primer](https://primer.style/product/ui-patterns/empty-states/) diff --git a/apps/design-system/next.config.mjs b/apps/design-system/next.config.mjs index 2280b564d2924..2c3beb53dca3e 100644 --- a/apps/design-system/next.config.mjs +++ b/apps/design-system/next.config.mjs @@ -26,16 +26,27 @@ const nextConfig = { return [ ...(BASE_PATH.length ? [ - { - source: '/', - destination: BASE_PATH, - basePath: false, - permanent: false, - }, - ] + { + source: '/', + destination: BASE_PATH, + basePath: false, + permanent: false, + }, + ] : []), ] }, + // Turbopack configuration to handle .md files with raw-loader + // This mirrors the webpack configuration added by withContentlayer + // and ensures both bundlers can process content files properly + turbopack: { + rules: { + '*.md': { + loaders: ['raw-loader'], + as: '*.js', + }, + }, + }, } export default withContentlayer(nextConfig) diff --git a/apps/design-system/registry/default/example/empty-state-initial-state.tsx b/apps/design-system/registry/default/example/empty-state-initial-state.tsx new file mode 100644 index 0000000000000..0c0db65716615 --- /dev/null +++ b/apps/design-system/registry/default/example/empty-state-initial-state.tsx @@ -0,0 +1,22 @@ +import { Button } from 'ui' +import { Plus } from 'lucide-react' +import { BucketAdd } from 'icons' + +export default function EmptyStateInitialState() { + return ( + + ) +} diff --git a/apps/design-system/registry/default/example/empty-state-missing-route.tsx b/apps/design-system/registry/default/example/empty-state-missing-route.tsx new file mode 100644 index 0000000000000..fca36ca0fd0da --- /dev/null +++ b/apps/design-system/registry/default/example/empty-state-missing-route.tsx @@ -0,0 +1,22 @@ +import { Admonition } from 'ui-patterns/admonition' +import Link from 'next/link' +import { Button } from 'ui' + +const bucketId = 'user_avatars' + +export default function EmptyStateMissingRoute() { + return ( +
+ + + +
+ ) +} diff --git a/apps/design-system/registry/default/example/empty-state-zero-items-table.tsx b/apps/design-system/registry/default/example/empty-state-zero-items-table.tsx new file mode 100644 index 0000000000000..67949539b132a --- /dev/null +++ b/apps/design-system/registry/default/example/empty-state-zero-items-table.tsx @@ -0,0 +1,25 @@ +import { Card, Table, TableBody, TableHead, TableHeader, TableRow, TableCell } from 'ui' + +export default function EmptyStateZeroItemsTable() { + return ( + + + + + Table name + Date created + + + + + + +

No tables yet

+

Connect a table from your database

+
+
+
+
+
+ ) +} diff --git a/apps/design-system/registry/examples.ts b/apps/design-system/registry/examples.ts index b47de24fed577..622605d689644 100644 --- a/apps/design-system/registry/examples.ts +++ b/apps/design-system/registry/examples.ts @@ -1263,4 +1263,19 @@ export const examples: Registry = [ type: 'components:example', files: ['example/logs-bar-chart.tsx'], }, + { + name: 'empty-state-missing-route', + type: 'components:example', + files: ['example/empty-state-missing-route.tsx'], + }, + { + name: 'empty-state-zero-items-table', + type: 'components:example', + files: ['example/empty-state-zero-items-table.tsx'], + }, + { + name: 'empty-state-initial-state', + type: 'components:example', + files: ['example/empty-state-initial-state.tsx'], + }, ] From 65b8ef91ac77fad476efd2f7da0b34e675a63bb6 Mon Sep 17 00:00:00 2001 From: Charis <26616127+charislam@users.noreply.github.com> Date: Tue, 4 Nov 2025 02:16:46 -0500 Subject: [PATCH 02/12] fix(troubleshooting sync) (#40096) A few troubleshooting guides aren't syncing properly, for miscellaneous issues: - Misformatted link - Edge cases with Markdown parsing: - GFM automatically wraps emails in angle brackets, which subsequently breaks MDX because it expects anything wrapped in angle brackets to be a JSX tag - toMarkdown inserts sentinel comments between certain blocks, which subsequently breaks MDX for the same reason --- ...ge-function-shutdown-reasons-explained.mdx | 1 - .../features/docs/Troubleshooting.script.mjs | 24 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/docs/content/troubleshooting/edge-function-shutdown-reasons-explained.mdx b/apps/docs/content/troubleshooting/edge-function-shutdown-reasons-explained.mdx index 67816d99b72ba..9b7a4a16cf670 100644 --- a/apps/docs/content/troubleshooting/edge-function-shutdown-reasons-explained.mdx +++ b/apps/docs/content/troubleshooting/edge-function-shutdown-reasons-explained.mdx @@ -2,7 +2,6 @@ title = "Edge Function shutdown reasons explained" topics = [ "functions" ] keywords = [ "shutdown", "termination", "event loop", "wall clock", "cpu time", "memory", "early drop" ] -database_id = "" [[errors]] http_status_code = 546 diff --git a/apps/docs/features/docs/Troubleshooting.script.mjs b/apps/docs/features/docs/Troubleshooting.script.mjs index e3cd431c50833..ff6e9eda6c785 100644 --- a/apps/docs/features/docs/Troubleshooting.script.mjs +++ b/apps/docs/features/docs/Troubleshooting.script.mjs @@ -258,37 +258,37 @@ async function updateChecksumIfNeeded(entry) { /** * Converts relative links to absolute URLs for GitHub discussions using MDAST - * @param {string} content - The markdown content to process + * @param {string} content - The markdown content to process (already stripped of JSX) */ function rewriteRelativeLinks(content) { const baseUrl = 'https://supabase.com' - + // Parse the markdown to AST const mdast = fromMarkdown(content, { - extensions: [gfm(), mdxjs()], - mdastExtensions: [gfmFromMarkdown(), mdxFromMarkdown()], + extensions: [gfm()], + mdastExtensions: [gfmFromMarkdown()], }) - + // Walk the tree and modify link nodes - /** - * @param {import('mdast').Root|import('mdast').Content} node - */ + /** + * @param {import('mdast').Root|import('mdast').Content} node + */ function visitNode(node) { if (node.type === 'link' && node.url && node.url.startsWith('/')) { // Convert relative URL to absolute node.url = `${baseUrl}${node.url}` } - + // Recursively visit children if ('children' in node) { node.children.forEach(visitNode) } } - + visitNode(mdast) - + // Convert back to markdown - return toMarkdown(mdast, { extensions: [gfmToMarkdown(), mdxToMarkdown()] }) + return toMarkdown(mdast, { extensions: [gfmToMarkdown()] }) } /** From 1f9906aab339cc0f641585e6c1e154faa31237db Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Tue, 4 Nov 2025 15:37:30 +0800 Subject: [PATCH 03/12] Use new Storage UI layout (#40076) * First pass of deprecating old storage UI components * Clean up local-storage flag for new storage UI * Add coming soon UI for analytics and vector buckets pages * Set up warning states for analytics buckets * fix spelling and margin * Smol fix * nit * Surface wrappers upgrade check in UI instead of toast * Nit clean up * update tests --------- Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com> --- .../FeaturePreview.constants.tsx | 7 - .../FeaturePreview/FeaturePreviewContext.tsx | 9 - .../FeaturePreview/FeaturePreviewModal.tsx | 4 +- .../FeaturePreview/NewStorageUIPreview.tsx | 42 -- .../DestinationPanelFields.tsx | 18 +- apps/studio/components/interfaces/Sidebar.tsx | 6 +- .../Storage/AnalyticsBucketDetails/index.tsx | 179 ++---- .../useAnalyticsBucketAssociatedEntities.tsx | 7 +- .../interfaces/Storage/AnalyticsBuckets.tsx | 38 +- .../interfaces/Storage/BucketsComingSoon.tsx | 36 ++ .../interfaces/Storage/CreateBucketModal.tsx | 547 ++++++------------ .../Storage/CreateSpecializedBucketModal.tsx | 75 ++- .../interfaces/Storage/DeleteBucketModal.tsx | 16 +- .../interfaces/Storage/EmptyBucketState.tsx | 12 +- .../interfaces/Storage/FilesBuckets/index.tsx | 2 +- .../StorageExplorer/FileExplorerHeader.tsx | 67 +-- .../StorageExplorer/useSelectedBucket.ts | 6 +- .../Storage/StorageMenu.BucketList.tsx | 95 --- .../interfaces/Storage/StorageMenu.tsx | 203 ------- .../interfaces/Storage/StorageMenuV2.tsx | 12 +- .../__tests__/CreateBucketModal.test.tsx | 7 - .../__tests__/DeleteBucketModal.test.tsx | 4 +- .../NavigationBar/NavigationBar.utils.tsx | 8 +- .../ProjectSettingsLayout/SettingsLayout.tsx | 3 +- .../layouts/StorageLayout/StorageLayout.tsx | 61 +- .../config/project-storage-config-query.ts | 5 + .../analytics-bucket-create-mutation.ts | 8 +- .../analytics-bucket-delete-mutation.ts | 8 +- .../data/storage/analytics-buckets-query.ts | 6 +- apps/studio/next.config.js | 12 +- .../storage/analytics/buckets/[bucketId].tsx | 2 +- .../project/[ref]/storage/analytics/index.tsx | 7 +- .../[ref]/storage/buckets/[bucketId].tsx | 48 -- .../project/[ref]/storage/buckets/index.tsx | 57 -- .../pages/project/[ref]/storage/index.tsx | 33 -- .../project/[ref]/storage/vectors/index.tsx | 4 +- packages/common/constants/local-storage.ts | 1 - packages/ui-patterns/src/admonition.tsx | 90 +-- 38 files changed, 480 insertions(+), 1265 deletions(-) delete mode 100644 apps/studio/components/interfaces/App/FeaturePreview/NewStorageUIPreview.tsx create mode 100644 apps/studio/components/interfaces/Storage/BucketsComingSoon.tsx delete mode 100644 apps/studio/components/interfaces/Storage/StorageMenu.BucketList.tsx delete mode 100644 apps/studio/components/interfaces/Storage/StorageMenu.tsx delete mode 100644 apps/studio/pages/project/[ref]/storage/buckets/[bucketId].tsx delete mode 100644 apps/studio/pages/project/[ref]/storage/buckets/index.tsx delete mode 100644 apps/studio/pages/project/[ref]/storage/index.tsx diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx index 03d791ca8914e..ce8d859e7852c 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx @@ -8,13 +8,6 @@ export const FEATURE_PREVIEWS = [ isNew: true, isPlatformOnly: true, }, - { - key: LOCAL_STORAGE_KEYS.UI_PREVIEW_NEW_STORAGE_UI, - name: 'New Storage interface', - discussionsUrl: undefined, - isNew: true, - isPlatformOnly: false, - }, { key: LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS, name: 'New Logs interface', diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx index bb2b449c4d512..248d731e5a705 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx @@ -110,11 +110,6 @@ export const useIsAdvisorRulesEnabled = () => { return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_ADVISOR_RULES] } -export const useIsNewStorageUIEnabled = () => { - const { flags } = useFeaturePreviewContext() - return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_NEW_STORAGE_UI] -} - export const useIsSecurityNotificationsEnabled = () => { const { flags } = useFeaturePreviewContext() return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_SECURITY_NOTIFICATIONS] @@ -126,7 +121,6 @@ export const useFeaturePreviewModal = () => { const gitlessBranchingEnabled = useFlag('gitlessBranching') const advisorRulesEnabled = useFlag('advisorRules') const isUnifiedLogsPreviewAvailable = useFlag('unifiedLogs') - const isNewStorageUIAvailable = useFlag('storageAnalyticsVector') const isSecurityNotificationsAvailable = useFlag('securityNotifications') const selectedFeatureKeyFromQuery = featurePreviewModal?.trim() ?? null @@ -142,8 +136,6 @@ export const useFeaturePreviewModal = () => { return advisorRulesEnabled case 'supabase-ui-preview-unified-logs': return isUnifiedLogsPreviewAvailable - case 'new-storage-ui': - return isNewStorageUIAvailable case 'security-notifications': return isSecurityNotificationsAvailable default: @@ -154,7 +146,6 @@ export const useFeaturePreviewModal = () => { gitlessBranchingEnabled, advisorRulesEnabled, isUnifiedLogsPreviewAvailable, - isNewStorageUIAvailable, isSecurityNotificationsAvailable, ] ) diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx index 3fa262e4b123e..524e7cd28e5af 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx @@ -14,9 +14,8 @@ import { CLSPreview } from './CLSPreview' import { FEATURE_PREVIEWS } from './FeaturePreview.constants' import { useFeaturePreviewContext, useFeaturePreviewModal } from './FeaturePreviewContext' import { InlineEditorPreview } from './InlineEditorPreview' -import { NewStorageUIPreview } from './NewStorageUIPreview' -import { UnifiedLogsPreview } from './UnifiedLogsPreview' import { SecurityNotificationsPreview } from './SecurityNotificationsPreview' +import { UnifiedLogsPreview } from './UnifiedLogsPreview' const FEATURE_PREVIEW_KEY_TO_CONTENT: { [key: string]: ReactNode @@ -27,7 +26,6 @@ const FEATURE_PREVIEW_KEY_TO_CONTENT: { [LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL]: , [LOCAL_STORAGE_KEYS.UI_PREVIEW_CLS]: , [LOCAL_STORAGE_KEYS.UI_PREVIEW_UNIFIED_LOGS]: , - [LOCAL_STORAGE_KEYS.UI_PREVIEW_NEW_STORAGE_UI]: , [LOCAL_STORAGE_KEYS.UI_PREVIEW_SECURITY_NOTIFICATIONS]: , } diff --git a/apps/studio/components/interfaces/App/FeaturePreview/NewStorageUIPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/NewStorageUIPreview.tsx deleted file mode 100644 index 6225b04d5237c..0000000000000 --- a/apps/studio/components/interfaces/App/FeaturePreview/NewStorageUIPreview.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import Image from 'next/image' - -import { useParams } from 'common' -import { InlineLink } from 'components/ui/InlineLink' -import { BASE_PATH } from 'lib/constants' -import { useIsNewStorageUIEnabled } from './FeaturePreviewContext' - -export const NewStorageUIPreview = () => { - const { ref } = useParams() - const isStorageV2 = useIsNewStorageUIEnabled() - - return ( -
-

- Experience our enhanced{' '} - - Storage interface - {' '} - with support for analytics and vector bucket types. -

- new-storage-preview -
-

Enabling this preview will:

-
    -
  • Move Storage buckets from the sidebar into the main content area
  • -
  • Change the role of the sidebar to a bucket type selector
  • -
  • Nest settings and policies under their respective bucket types
  • -
-

- These changes are necessary to support incoming analytics and vector bucket types. File - storage will remain the default, and be shown by default when entering Storage. -

-
-
- ) -} diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanelFields.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanelFields.tsx index 283c180190b69..54f871f94b224 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanelFields.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationPanel/DestinationPanelFields.tsx @@ -3,7 +3,6 @@ import { useMemo, useState } from 'react' import type { UseFormReturn } from 'react-hook-form' import { useParams } from 'common' -import { useIsNewStorageUIEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { getCatalogURI } from 'components/interfaces/Storage/StorageSettings/StorageSettings.utils' import { InlineLink } from 'components/ui/InlineLink' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' @@ -124,7 +123,6 @@ export const AnalyticsBucketFields = ({ const [showSecretAccessKey, setShowSecretAccessKey] = useState(false) const { ref: projectRef } = useParams() - const isStorageV2 = useIsNewStorageUIEnabled() const { data: project } = useSelectedProjectQuery() const { data: apiKeys } = useAPIKeysQuery({ projectRef, reveal: true }) @@ -447,13 +445,7 @@ export const AnalyticsBucketFields = ({

Please select another key or create a new set, as this destination will not work otherwise. S3 access keys can be managed in your{' '} - + storage settings

@@ -466,13 +458,7 @@ export const AnalyticsBucketFields = ({

S3 access keys can be managed in your{' '} - + storage settings . diff --git a/apps/studio/components/interfaces/Sidebar.tsx b/apps/studio/components/interfaces/Sidebar.tsx index ab0edd298e8e5..b8ce158ee6378 100644 --- a/apps/studio/components/interfaces/Sidebar.tsx +++ b/apps/studio/components/interfaces/Sidebar.tsx @@ -5,7 +5,7 @@ import Link from 'next/link' import { useRouter } from 'next/router' import { ComponentProps, ComponentPropsWithoutRef, FC, ReactNode, useEffect } from 'react' -import { LOCAL_STORAGE_KEYS, useIsMFAEnabled, useParams } from 'common' +import { LOCAL_STORAGE_KEYS, useFlag, useIsMFAEnabled, useParams } from 'common' import { generateOtherRoutes, generateProductRoutes, @@ -44,10 +44,8 @@ import { } from 'ui' import { useIsAPIDocsSidePanelEnabled, - useIsNewStorageUIEnabled, useUnifiedLogsPreview, } from './App/FeaturePreview/FeaturePreviewContext' -import { useFlag } from 'common' export const ICON_SIZE = 32 export const ICON_STROKE_WIDTH = 1.5 @@ -231,7 +229,6 @@ const ProjectLinks = () => { const { mutate: sendEvent } = useSendEventMutation() const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled() - const isStorageV2 = useIsNewStorageUIEnabled() const { isEnabled: isUnifiedLogsEnabled } = useUnifiedLogsPreview() const activeRoute = router.pathname.split('/')[3] @@ -257,7 +254,6 @@ const ProjectLinks = () => { storage: storageEnabled, realtime: realtimeEnabled, authOverviewPage: authOverviewPageEnabled, - isStorageV2, }) const otherRoutes = generateOtherRoutes(ref, project, { unifiedLogs: isUnifiedLogsEnabled, diff --git a/apps/studio/components/interfaces/Storage/AnalyticsBucketDetails/index.tsx b/apps/studio/components/interfaces/Storage/AnalyticsBucketDetails/index.tsx index 5685cc1affe00..de7b2a1d9c0be 100644 --- a/apps/studio/components/interfaces/Storage/AnalyticsBucketDetails/index.tsx +++ b/apps/studio/components/interfaces/Storage/AnalyticsBucketDetails/index.tsx @@ -3,7 +3,6 @@ import { SquarePlus } from 'lucide-react' import Link from 'next/link' import { useMemo, useState } from 'react' -import { useIsNewStorageUIEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { INTEGRATIONS } from 'components/interfaces/Integrations/Landing/Integrations.constants' import { WrapperMeta } from 'components/interfaces/Integrations/Wrappers/Wrappers.types' import { @@ -31,17 +30,7 @@ import { useIcebergWrapperCreateMutation } from 'data/storage/iceberg-wrapper-cr import { useVaultSecretDecryptedValueQuery } from 'data/vault/vault-secret-decrypted-value-query' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { DOCS_URL } from 'lib/constants' -import { - Button, - Card, - CardContent, - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from 'ui' +import { Button, Card, CardContent } from 'ui' import { Admonition } from 'ui-patterns/admonition' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { DeleteBucketModal } from '../DeleteBucketModal' @@ -49,7 +38,6 @@ import { ConnectTablesDialog } from './ConnectTablesDialog' import { DESCRIPTIONS, LABELS, OPTION_ORDER } from './constants' import { CopyEnvButton } from './CopyEnvButton' import { DecryptedReadOnlyInput } from './DecryptedReadOnlyInput' -import { NamespaceRow } from './NamespaceRow' import { NamespaceWithTables } from './NamespaceWithTables' import { SimpleConfigurationDetails } from './SimpleConfigurationDetails' import { useAnalyticsBucketWrapperInstance } from './useAnalyticsBucketWrapperInstance' @@ -58,7 +46,6 @@ import { useIcebergWrapperExtension } from './useIcebergWrapper' export const AnalyticBucketDetails = ({ bucket }: { bucket: AnalyticsBucket }) => { const config = BUCKET_TYPES.analytics const [modal, setModal] = useState<'delete' | null>(null) - const isStorageV2 = useIsNewStorageUIEnabled() const { data: project } = useSelectedProjectQuery() const { state: extensionState } = useIcebergWrapperExtension() @@ -128,16 +115,12 @@ export const AnalyticBucketDetails = ({ bucket }: { bucket: AnalyticsBucket }) = <> ] : []} > @@ -165,117 +148,49 @@ export const AnalyticBucketDetails = ({ bucket }: { bucket: AnalyticsBucket }) = {state === 'added' && wrapperInstance && ( <> - {isStorageV2 ? ( - - -

- Tables - - Analytics tables stored in this bucket - -
- {namespaces.length > 0 && } - - - {isLoadingNamespaces || isLoading ? ( - - ) : namespaces.length === 0 ? ( - - ) : ( -
- {namespaces.map(({ namespace, schema, tables }) => ( - - ))} -
- )} - - ) : ( - - - Namespaces + + +
+ Tables - Connected namespaces and tables. + Analytics tables stored in this bucket - +
+ {namespaces.length > 0 && } +
- {isLoadingNamespaces || isLoading ? ( - - ) : namespaces.length === 0 ? ( - - - - - Namespace - Schema - Tables - - - - - - -

- No namespaces in this bucket -

-

- Create a namespace and add some data -

-
-
-
-
-
- ) : ( - - - - - Namespace - Schema - Tables - - - - - {namespaces.map(({ namespace, schema, tables }) => ( - - ))} - -
-
- )} -
- )} + {isLoadingNamespaces || isLoading ? ( + + ) : namespaces.length === 0 ? ( + + ) : ( +
+ {namespaces.map(({ namespace, schema, tables }) => ( + + ))} +
+ )} +
diff --git a/apps/studio/components/interfaces/Storage/AnalyticsBucketDetails/useAnalyticsBucketAssociatedEntities.tsx b/apps/studio/components/interfaces/Storage/AnalyticsBucketDetails/useAnalyticsBucketAssociatedEntities.tsx index e75b00d6117b1..8aa21db79fbd8 100644 --- a/apps/studio/components/interfaces/Storage/AnalyticsBucketDetails/useAnalyticsBucketAssociatedEntities.tsx +++ b/apps/studio/components/interfaces/Storage/AnalyticsBucketDetails/useAnalyticsBucketAssociatedEntities.tsx @@ -1,6 +1,5 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useIsNewStorageUIEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { WrapperMeta } from 'components/interfaces/Integrations/Wrappers/Wrappers.types' import { useFDWDeleteMutation } from 'data/fdw/fdw-delete-mutation' import { FDW } from 'data/fdw/fdws-query' @@ -26,10 +25,6 @@ export const useAnalyticsBucketAssociatedEntities = ( { projectRef, bucketId }: { projectRef?: string; bucketId: string }, options: { enabled: boolean } = { enabled: true } ) => { - // [Joshen] Opting to skip cleaning up ETL related entities within old UI - // Also to prevent an unnecessary call to /sources for existing UI - const isStorageV2 = useIsNewStorageUIEnabled() - const { can: canReadS3Credentials } = useAsyncCheckPermissions( PermissionAction.STORAGE_ADMIN_READ, '*' @@ -50,7 +45,7 @@ export const useAnalyticsBucketAssociatedEntities = ( const { data: sourcesData } = useReplicationSourcesQuery( { projectRef }, - { enabled: isStorageV2 && options.enabled } + { enabled: options.enabled } ) const sourceId = sourcesData?.sources.find((s) => s.name === projectRef)?.id diff --git a/apps/studio/components/interfaces/Storage/AnalyticsBuckets.tsx b/apps/studio/components/interfaces/Storage/AnalyticsBuckets.tsx index 84abf828ce690..b95c0daba94d8 100644 --- a/apps/studio/components/interfaces/Storage/AnalyticsBuckets.tsx +++ b/apps/studio/components/interfaces/Storage/AnalyticsBuckets.tsx @@ -1,6 +1,5 @@ -import { MoreVertical, Search, Trash2 } from 'lucide-react' +import { ExternalLink, MoreVertical, Search, Trash2 } from 'lucide-react' import Link from 'next/link' -import { useRouter } from 'next/router' import { useState } from 'react' import { useParams } from 'common' @@ -21,14 +20,13 @@ import { TableHeader, TableRow, } from 'ui' -import { TimestampInfo } from 'ui-patterns' +import { Admonition, TimestampInfo } from 'ui-patterns' import { Input } from 'ui-patterns/DataInputs/Input' import { CreateSpecializedBucketModal } from './CreateSpecializedBucketModal' import { DeleteBucketModal } from './DeleteBucketModal' import { EmptyBucketState } from './EmptyBucketState' export const AnalyticsBuckets = () => { - const router = useRouter() const { ref } = useParams() const [filterString, setFilterString] = useState('') @@ -44,14 +42,36 @@ export const AnalyticsBuckets = () => { ) return ( - <> + + }> + + Leave feedback + + + } + > +

+ Expect rapid changes, limited features, and possible breaking updates as we expand access. +

+

Please share feedback as we refine the experience!

+
+ {!isLoadingBuckets && buckets.filter((bucket) => !('type' in bucket) || bucket.type === 'ANALYTICS').length === 0 ? ( ) : ( - // Override the default first:pt-12 to match other storage types - +
Buckets @@ -143,7 +163,7 @@ export const AnalyticsBuckets = () => { )} - +
)} {selectedBucket && ( @@ -153,6 +173,6 @@ export const AnalyticsBuckets = () => { onClose={() => setModal(null)} /> )} - +
) } diff --git a/apps/studio/components/interfaces/Storage/BucketsComingSoon.tsx b/apps/studio/components/interfaces/Storage/BucketsComingSoon.tsx new file mode 100644 index 0000000000000..30416d60e81ba --- /dev/null +++ b/apps/studio/components/interfaces/Storage/BucketsComingSoon.tsx @@ -0,0 +1,36 @@ +import { ScaffoldSection } from 'components/layouts/Scaffold' +import { Bucket } from 'icons' +import { ExternalLink } from 'lucide-react' +import Link from 'next/link' +import { Button } from 'ui' + +export const BucketsComingSoon = ({ type }: { type: 'analytics' | 'vector' }) => { + return ( + + + + ) +} diff --git a/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx b/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx index c9579551809b8..f816ff023922e 100644 --- a/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/CreateBucketModal.tsx @@ -1,30 +1,22 @@ import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { snakeCase } from 'lodash' import { Plus } from 'lucide-react' -import { useRouter } from 'next/router' import { useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import z from 'zod' import { useParams } from 'common' -import { useIcebergWrapperExtension } from 'components/interfaces/Storage/AnalyticsBucketDetails/useIcebergWrapper' import { StorageSizeUnits } from 'components/interfaces/Storage/StorageSettings/StorageSettings.constants' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { InlineLink } from 'components/ui/InlineLink' import { useProjectStorageConfigQuery } from 'data/config/project-storage-config-query' -import { useAnalyticsBucketCreateMutation } from 'data/storage/analytics-bucket-create-mutation' import { useBucketCreateMutation } from 'data/storage/bucket-create-mutation' -import { useIcebergWrapperCreateMutation } from 'data/storage/iceberg-wrapper-create-mutation' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' -import { BASE_PATH, IS_PLATFORM } from 'lib/constants' +import { IS_PLATFORM } from 'lib/constants' import { - Alert_Shadcn_, - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, Button, Dialog, DialogContent, @@ -39,22 +31,16 @@ import { FormField_Shadcn_, FormMessage_Shadcn_, Input_Shadcn_, - Label_Shadcn_, - RadioGroupStacked, - RadioGroupStackedItem, Select_Shadcn_, SelectContent_Shadcn_, SelectItem_Shadcn_, SelectTrigger_Shadcn_, SelectValue_Shadcn_, Switch, - WarningIcon, } from 'ui' import { Admonition } from 'ui-patterns/admonition' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { useIsNewStorageUIEnabled } from '../App/FeaturePreview/FeaturePreviewContext' import { inverseValidBucketNameRegex, validBucketNameRegex } from './CreateBucketModal.utils' -import { BUCKET_TYPES } from './Storage.constants' import { convertFromBytes, convertToBytes } from './StorageSettings/StorageSettings.utils' const FormSchema = z @@ -72,7 +58,6 @@ const FormSchema = z (value) => value !== 'public', '"public" is a reserved name. Please choose another name' ), - type: z.enum(['STANDARD', 'ANALYTICS']).default('STANDARD'), public: z.boolean().default(false), has_file_size_limit: z.boolean().default(false), formatted_size_limit: z.coerce @@ -111,66 +96,42 @@ export const CreateBucketModal = ({ buttonClassName, label = 'New bucket', }: CreateBucketModalProps) => { - const router = useRouter() const { ref } = useParams() const { data: org } = useSelectedOrganizationQuery() - const isStorageV2 = useIsNewStorageUIEnabled() const [visible, setVisible] = useState(false) const [selectedUnit, setSelectedUnit] = useState(StorageSizeUnits.MB) + const [hasAllowedMimeTypes, setHasAllowedMimeTypes] = useState(false) const { can: canCreateBuckets } = useAsyncCheckPermissions(PermissionAction.STORAGE_WRITE, '*') + const { data } = useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM }) + const { value, unit } = convertFromBytes(data?.fileSizeLimit ?? 0) + const formattedGlobalUploadLimit = `${value} ${unit}` + const { mutate: sendEvent } = useSendEventMutation() const { mutateAsync: createBucket, isLoading: isCreatingBucket } = useBucketCreateMutation({ // [Joshen] Silencing the error here as it's being handled in onSubmit onError: () => {}, }) - const { mutateAsync: createAnalyticsBucket, isLoading: isCreatingAnalyticsBucket } = - useAnalyticsBucketCreateMutation({ - // [Joshen] Silencing the error here as it's being handled in onSubmit - onError: () => {}, - }) - const { mutateAsync: createIcebergWrapper, isLoading: isCreatingIcebergWrapper } = - useIcebergWrapperCreateMutation() - - const { data } = useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM }) - const { value, unit } = convertFromBytes(data?.fileSizeLimit ?? 0) - const formattedGlobalUploadLimit = `${value} ${unit}` - - const config = BUCKET_TYPES['files'] - const isCreating = isCreatingBucket || isCreatingAnalyticsBucket const form = useForm({ resolver: zodResolver(FormSchema), defaultValues: { name: '', public: false, - type: 'STANDARD', has_file_size_limit: false, formatted_size_limit: undefined, allowed_mime_types: '', }, }) const { formatted_size_limit: formattedSizeLimitError } = form.formState.errors - - const bucketName = snakeCase(form.watch('name')) const isPublicBucket = form.watch('public') - const isStandardBucket = form.watch('type') === 'STANDARD' const hasFileSizeLimit = form.watch('has_file_size_limit') - const [hasAllowedMimeTypes, setHasAllowedMimeTypes] = useState(false) - const { state: icebergWrapperExtensionState } = useIcebergWrapperExtension() - const icebergCatalogEnabled = data?.features?.icebergCatalog?.enabled const onSubmit: SubmitHandler = async (values) => { if (!ref) return console.error('Project ref is required') - if (values.type === 'ANALYTICS' && !icebergCatalogEnabled) { - return toast.error( - 'The Analytics catalog feature is not enabled for your project. Please contact support to enable it.' - ) - } - // [Joshen] Should shift this into superRefine in the form schema try { const fileSizeLimit = @@ -190,37 +151,24 @@ export const CreateBucketModal = ({ }) } - if (values.type === 'STANDARD') { - await createBucket({ - projectRef: ref, - id: values.name, - type: 'STANDARD', - isPublic: values.public, - file_size_limit: fileSizeLimit, - allowed_mime_types: allowedMimeTypes, - }) - } else if (values.type === 'ANALYTICS') { - await createAnalyticsBucket({ - projectRef: ref, - bucketName: values.name, - }) - if (icebergWrapperExtensionState === 'installed') { - await createIcebergWrapper({ bucketName: values.name }) - } - } - + await createBucket({ + projectRef: ref, + id: values.name, + type: 'STANDARD', + isPublic: values.public, + file_size_limit: fileSizeLimit, + allowed_mime_types: allowedMimeTypes, + }) sendEvent({ action: 'storage_bucket_created', - properties: { bucketType: values.type }, + properties: { bucketType: 'STANDARD' }, groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown' }, }) toast.success(`Successfully created bucket ${values.name}`) form.reset() - setSelectedUnit(StorageSizeUnits.MB) setVisible(false) - if (!isStorageV2) router.push(`/project/${ref}/storage/buckets/${values.name}`) } catch (error: any) { // Handle specific error cases for inline display const errorMessage = error.message?.toLowerCase() || '' @@ -281,7 +229,7 @@ export const CreateBucketModal = ({ - Create a {isStorageV2 ? config.singularName : 'storage'} bucket + Create a storage bucket @@ -313,344 +261,197 @@ export const CreateBucketModal = ({ )} /> + - {!isStorageV2 && ( - ( - - - field.onChange(v)} - > - - {IS_PLATFORM && ( - - <> -

- Stores Iceberg files and is optimized for analytical workloads. -

- - {icebergCatalogEnabled ? null : ( -
- - - This feature is currently in alpha and not yet enabled for - your project. Sign up{' '} - - here - - . - -
- )} - -
- )} -
-
-
- )} + + + + ( + + + + + + )} + /> + {isPublicBucket && ( + )} - {isStandardBucket ? ( - <> - + + ( + + + + + + )} + /> + + {hasFileSizeLimit && ( +
( - - - +
+
+ + + +
+
+ + + {selectedUnit} + + + {Object.values(StorageSizeUnits).map((unit: string) => ( + + {unit} + + ))} + + +
+
)} /> - {isPublicBucket && ( - + {formattedSizeLimitError?.message === 'exceed_global_limit' && ( + + Exceeds global limit of {formattedGlobalUploadLimit}. Increase limit in{' '} + setVisible(false)} + > + Storage Settings + {' '} + first. + )} - - - - - ( - + This project has a{' '} + setVisible(false)} > - - - - - )} - /> - - {hasFileSizeLimit && ( -
- ( - -
-
- - - -
-
- - - {selectedUnit} - - - {Object.values(StorageSizeUnits).map((unit: string) => ( - - {unit} - - ))} - - -
-
-
- )} - /> - {formattedSizeLimitError?.message === 'exceed_global_limit' && ( - - Exceeds global limit of {formattedGlobalUploadLimit}. Increase limit in{' '} - setVisible(false)} - > - Storage Settings - {' '} - first. - - )} - - {IS_PLATFORM && ( -

- This project has a{' '} - setVisible(false)} - > - global file size limit - {' '} - of {formattedGlobalUploadLimit}. -

- )} -
+ global file size limit + {' '} + of {formattedGlobalUploadLimit}. +

)} -
+
+ )} +
- + - - - - - - - {hasAllowedMimeTypes && ( - + + + + + + {hasAllowedMimeTypes && ( + ( + ( - - - - - - )} - /> + label="Allowed MIME types" + labelOptional="Comma separated values" + description="Wildcards are allowed, e.g. image/*." + > + + + + )} - - - ) : ( - <> - {icebergWrapperExtensionState === 'installed' ? ( - - -

- Supabase will setup a - - foreign data wrapper - {bucketName && {`${bucketName}_fdw`}} - - - {' '} - for easier access to the data. This action will also create{' '} - - S3 Access Keys - {bucketName && ( - <> - {' '} - named {`${bucketName}_keys`} - - )} - - and - - four Vault Secrets - {bucketName && ( - <> - {' '} - prefixed with{' '} - {`${bucketName}_vault_`} - - )} - - . - -

-

- As a final step, you'll need to create an{' '} - Iceberg namespace before you - connect the Iceberg data to your database. -

-
-
- ) : ( - - - - You need to install the Iceberg wrapper extension to connect your Analytic - bucket to your database. - - -

- You need to install the wrappers{' '} - extension (with the minimum version of 0.5.3) if you want to - connect your Analytics bucket to your database. -

-
-
- )} - - )} + /> + )} +
- diff --git a/apps/studio/components/interfaces/Storage/CreateSpecializedBucketModal.tsx b/apps/studio/components/interfaces/Storage/CreateSpecializedBucketModal.tsx index 3208dfedd624d..184019ce0a3c1 100644 --- a/apps/studio/components/interfaces/Storage/CreateSpecializedBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/CreateSpecializedBucketModal.tsx @@ -108,6 +108,7 @@ export const CreateSpecializedBucketModal = ({ const { data } = useProjectStorageConfigQuery({ projectRef: ref }, { enabled: IS_PLATFORM }) const icebergCatalogEnabled = data?.features?.icebergCatalog?.enabled + const wrappersExtenstionNeedsUpgrading = wrappersExtensionState === 'needs-upgrade' const { mutate: sendEvent } = useSendEventMutation() @@ -136,21 +137,6 @@ export const CreateSpecializedBucketModal = ({ if (!project) return console.error('Project details is required') if (!wrappersExtension) return console.error('Unable to find wrappers extension') - if (wrappersExtensionState === 'needs-upgrade') { - // [Joshen] Double check if this is the right CTA - return toast.error( -

- Wrappers extensions needs to be updated to create an Iceberg Wrapper. Update the extension - by disabling and enabling the wrappers extension first in - the{' '} - - database extensions page - {' '} - before creating an Analytics bucket. -

- ) - } - try { if (bucketType === 'analytics') { await createAnalyticsBucket({ @@ -227,7 +213,7 @@ export const CreateSpecializedBucketModal = ({ - + Create {config.singularName} bucket @@ -264,17 +250,43 @@ export const CreateSpecializedBucketModal = ({ /> {bucketType === 'analytics' && ( - -

- Supabase will install the{' '} - {wrappersExtensionState !== 'installed' ? 'Wrappers extension and ' : ''} - Iceberg Wrapper integration on your behalf.{' '} - - Learn more - - . -

-
+ <> + {wrappersExtenstionNeedsUpgrading ? ( + +

+ Update the wrappers extension by disabling + and enabling it in{' '} + + database extensions + {' '} + before creating an Analytics bucket.{' '} + + Learn more + + . +

+
+ ) : ( + +

+ Supabase will install the{' '} + {wrappersExtensionState !== 'installed' ? 'Wrappers extension and ' : ''} + Iceberg Wrapper integration on your behalf.{' '} + + Learn more + + . +

+
+ )} + )} @@ -284,8 +296,13 @@ export const CreateSpecializedBucketModal = ({ -
diff --git a/apps/studio/components/interfaces/Storage/DeleteBucketModal.tsx b/apps/studio/components/interfaces/Storage/DeleteBucketModal.tsx index 495981485df2f..d4a723180b757 100644 --- a/apps/studio/components/interfaces/Storage/DeleteBucketModal.tsx +++ b/apps/studio/components/interfaces/Storage/DeleteBucketModal.tsx @@ -29,7 +29,6 @@ import { } from 'ui' import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { useIsNewStorageUIEnabled } from '../App/FeaturePreview/FeaturePreviewContext' import { useAnalyticsBucketAssociatedEntities, useAnalyticsBucketDeleteCleanUp, @@ -46,9 +45,8 @@ const formId = `delete-storage-bucket-form` export const DeleteBucketModal = ({ visible, bucket, onClose }: DeleteBucketModalProps) => { const router = useRouter() - const { ref: projectRef } = useParams() + const { ref: projectRef, bucketId } = useParams() const { data: project } = useSelectedProjectQuery() - const isStorageV2 = useIsNewStorageUIEnabled() const isStandardBucketSelected = 'type' in bucket && bucket.type === 'STANDARD' @@ -107,11 +105,7 @@ export const DeleteBucketModal = ({ visible, bucket, onClose }: DeleteBucketModa ) toast.success(`Successfully deleted bucket ${bucket.id}`) - if (isStorageV2) { - router.push(`/project/${projectRef}/storage/files`) - } else { - router.push(`/project/${projectRef}/storage/buckets`) - } + if (!!bucketId) router.push(`/project/${projectRef}/storage/files`) onClose() } catch (error) { toast.success( @@ -139,11 +133,7 @@ export const DeleteBucketModal = ({ visible, bucket, onClose }: DeleteBucketModa }) } toast.success(`Successfully deleted analytics bucket ${bucket.id}`) - if (isStorageV2) { - router.push(`/project/${projectRef}/storage/analytics`) - } else { - router.push(`/project/${projectRef}/storage/buckets`) - } + if (!!bucketId) router.push(`/project/${projectRef}/storage/analytics`) onClose() }, }) diff --git a/apps/studio/components/interfaces/Storage/EmptyBucketState.tsx b/apps/studio/components/interfaces/Storage/EmptyBucketState.tsx index dde4c6e3166a0..56ca9f1759294 100644 --- a/apps/studio/components/interfaces/Storage/EmptyBucketState.tsx +++ b/apps/studio/components/interfaces/Storage/EmptyBucketState.tsx @@ -1,18 +1,26 @@ import { BucketAdd } from 'icons' +import { cn } from 'ui' import { CreateBucketModal } from './CreateBucketModal' import { CreateSpecializedBucketModal } from './CreateSpecializedBucketModal' import { BUCKET_TYPES } from './Storage.constants' interface EmptyBucketStateProps { bucketType: keyof typeof BUCKET_TYPES + className?: string } -export const EmptyBucketState = ({ bucketType }: EmptyBucketStateProps) => { +export const EmptyBucketState = ({ bucketType, className }: EmptyBucketStateProps) => { const config = BUCKET_TYPES[bucketType] return ( -