diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx
index f7448acba9807..9a56fd87b21e4 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreview.constants.tsx
@@ -1,9 +1,16 @@
import { LOCAL_STORAGE_KEYS } from 'common'
export const FEATURE_PREVIEWS = [
+ {
+ 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',
+ name: 'New Logs interface',
discussionsUrl: 'https://github.com/orgs/supabase/discussions/37234',
isNew: true,
isPlatformOnly: true,
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
index e33348dd18dc4..885724aca13c0 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx
@@ -110,12 +110,18 @@ 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 useFeaturePreviewModal = () => {
const [featurePreviewModal, setFeaturePreviewModal] = useQueryState('featurePreviewModal')
const gitlessBranchingEnabled = useFlag('gitlessBranching')
const advisorRulesEnabled = useFlag('advisorRules')
const isUnifiedLogsPreviewAvailable = useFlag('unifiedLogs')
+ const isNewStorageUIAvailable = useFlag('storageAnalyticsVector')
const selectedFeatureKeyFromQuery = featurePreviewModal?.trim() ?? null
const showFeaturePreviewModal = selectedFeatureKeyFromQuery !== null
@@ -130,6 +136,8 @@ export const useFeaturePreviewModal = () => {
return advisorRulesEnabled
case 'supabase-ui-preview-unified-logs':
return isUnifiedLogsPreviewAvailable
+ case 'new-storage-ui':
+ return isNewStorageUIAvailable
default:
return true
}
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx
index 751c08f92c961..ec463be8a6477 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx
@@ -14,6 +14,7 @@ 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'
const FEATURE_PREVIEW_KEY_TO_CONTENT: {
@@ -25,6 +26,7 @@ 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]: ,
}
const FeaturePreviewModal = () => {
diff --git a/apps/studio/components/interfaces/App/FeaturePreview/NewStorageUIPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/NewStorageUIPreview.tsx
new file mode 100644
index 0000000000000..6225b04d5237c
--- /dev/null
+++ b/apps/studio/components/interfaces/App/FeaturePreview/NewStorageUIPreview.tsx
@@ -0,0 +1,42 @@
+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.
+
+
+
+
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/App/FeaturePreview/UnifiedLogsPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/UnifiedLogsPreview.tsx
index b757cba8f45a1..0edff67d49fe8 100644
--- a/apps/studio/components/interfaces/App/FeaturePreview/UnifiedLogsPreview.tsx
+++ b/apps/studio/components/interfaces/App/FeaturePreview/UnifiedLogsPreview.tsx
@@ -16,7 +16,7 @@ export const UnifiedLogsPreview = () => {
className="rounded border mb-4"
/>
- Experience our enhanced logs interface with improved filtering, real-time updates, and a
+ Experience our enhanced Logs interface with improved filtering, real-time updates, and a
unified view across all your services. Built for better performance and easier debugging.
@@ -26,8 +26,8 @@ export const UnifiedLogsPreview = () => {
Enabling this preview will:
- Replace the current logs interface on the{' '}
- logs page with a unified view
+ Replace the current Logs interface on the{' '}
+ Logs page with a unified view
Provide enhanced filtering capabilities and real-time log streaming
Improve performance with optimized data loading and virtualization
diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx
index 186616f953ee6..b7a152514bebb 100644
--- a/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx
+++ b/apps/studio/components/interfaces/Functions/EdgeFunctionsListItem.tsx
@@ -4,10 +4,9 @@ import { useRouter } from 'next/router'
import { useState } from 'react'
import { useParams } from 'common/hooks'
-import Table from 'components/to-be-cleaned/Table'
+import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
import type { EdgeFunctionsResponse } from 'data/edge-functions/edge-functions-query'
-import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { copyToClipboard, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
interface EdgeFunctionsListItemProps {
@@ -17,20 +16,17 @@ interface EdgeFunctionsListItemProps {
export const EdgeFunctionsListItem = ({ function: item }: EdgeFunctionsListItemProps) => {
const router = useRouter()
const { ref } = useParams()
- const { data: project } = useSelectedProjectQuery()
const [isCopied, setIsCopied] = useState(false)
+ const { data: settings } = useProjectSettingsV2Query({ projectRef: ref })
const { data: customDomainData } = useCustomDomainsQuery({ projectRef: ref })
- // get the .co or .net TLD from the restUrl
- const restUrl = project?.restUrl
- const restUrlTld = restUrl !== undefined ? new URL(restUrl).hostname.split('.').pop() : 'co'
- const functionUrl = `https://${ref}.supabase.${restUrlTld}/functions/v1/${item.slug}`
-
- const endpoint =
+ const protocol = settings?.app_config?.protocol ?? 'https'
+ const endpoint = settings?.app_config?.endpoint ?? ''
+ const functionUrl =
customDomainData?.customDomain?.status === 'active'
? `https://${customDomainData.customDomain.hostname}/functions/v1/${item.slug}`
- : functionUrl
+ : `${protocol}://${endpoint}/functions/v1/${item.slug}`
return (
-
- {endpoint}
+
+ {functionUrl}
setIsCopied(false), 3000)
}
event.stopPropagation()
- onCopy(endpoint)
+ onCopy(functionUrl)
}}
>
{isCopied ? (
diff --git a/apps/studio/components/interfaces/Sidebar.tsx b/apps/studio/components/interfaces/Sidebar.tsx
index a4a7b9b4ea50e..63c0ee2600e15 100644
--- a/apps/studio/components/interfaces/Sidebar.tsx
+++ b/apps/studio/components/interfaces/Sidebar.tsx
@@ -43,6 +43,7 @@ import {
} from 'ui'
import {
useIsAPIDocsSidePanelEnabled,
+ useIsNewStorageUIEnabled,
useUnifiedLogsPreview,
} from './App/FeaturePreview/FeaturePreviewContext'
@@ -222,10 +223,13 @@ const ProjectLinks = () => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const snap = useAppStateSnapshot()
- const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled()
const { securityLints, errorLints } = useLints()
const showReports = useIsFeatureEnabled('reports:all')
+ const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled()
+ const isStorageV2 = useIsNewStorageUIEnabled()
+ const { isEnabled: isUnifiedLogsEnabled } = useUnifiedLogsPreview()
+
const activeRoute = router.pathname.split('/')[3]
const {
@@ -246,10 +250,8 @@ const ProjectLinks = () => {
edgeFunctions: edgeFunctionsEnabled,
storage: storageEnabled,
realtime: realtimeEnabled,
+ isStorageV2,
})
-
- const { isEnabled: isUnifiedLogsEnabled } = useUnifiedLogsPreview()
-
const otherRoutes = generateOtherRoutes(ref, project, {
unifiedLogs: isUnifiedLogsEnabled,
showReports,
diff --git a/apps/studio/components/interfaces/Storage/Storage.constants.ts b/apps/studio/components/interfaces/Storage/Storage.constants.ts
index 674153a2cc0d0..9dc6cae7b37b2 100644
--- a/apps/studio/components/interfaces/Storage/Storage.constants.ts
+++ b/apps/studio/components/interfaces/Storage/Storage.constants.ts
@@ -1,3 +1,6 @@
+import { DOCS_URL } from 'lib/constants'
+
+// Original storage constants
export enum URL_EXPIRY_DURATION {
WEEK = 60 * 60 * 24 * 7,
MONTH = 60 * 60 * 24 * 30,
@@ -56,3 +59,26 @@ export const CONTEXT_MENU_KEYS = {
STORAGE_ITEM: 'STORAGE_ITEM',
STORAGE_FOLDER: 'STORAGE_FOLDER',
}
+
+// New bucket types configuration
+
+export const BUCKET_TYPES = {
+ files: {
+ displayName: 'Files',
+ description: 'General file storage for most types of digital content.',
+ docsUrl: `${DOCS_URL}/guides/storage/buckets/fundamentals`,
+ },
+ analytics: {
+ displayName: 'Analytics',
+ description: 'Purpose-built storage for analytical workloads.',
+ docsUrl: `${DOCS_URL}/guides/storage/analytics/introduction`,
+ },
+ vectors: {
+ displayName: 'Vectors',
+ description: 'Purpose-built storage for vector data.',
+ docsUrl: `${DOCS_URL}/guides/storage/vectors`,
+ },
+}
+
+export const BUCKET_TYPE_KEYS = Object.keys(BUCKET_TYPES) as Array
+export const DEFAULT_BUCKET_TYPE: keyof typeof BUCKET_TYPES = 'files'
diff --git a/apps/studio/components/interfaces/Storage/StorageMenu.tsx b/apps/studio/components/interfaces/Storage/StorageMenu.tsx
index 86f85653f4dc9..40dca82bbfe2f 100644
--- a/apps/studio/components/interfaces/Storage/StorageMenu.tsx
+++ b/apps/studio/components/interfaces/Storage/StorageMenu.tsx
@@ -7,6 +7,7 @@ import { CreateBucketModal } from 'components/interfaces/Storage/CreateBucketMod
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useBucketsQuery } from 'data/storage/buckets-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import { IS_PLATFORM } from 'lib/constants'
import { useStorageExplorerStateSnapshot } from 'state/storage-explorer'
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Menu } from 'ui'
import {
@@ -16,10 +17,9 @@ import {
InnerSideBarFilterSortDropdown,
InnerSideBarFilterSortDropdownItem,
} from 'ui-patterns/InnerSideMenu'
-import { IS_PLATFORM } from 'lib/constants'
import { BucketRow } from './BucketRow'
-const StorageMenu = () => {
+export const StorageMenu = () => {
const router = useRouter()
const { ref, bucketId } = useParams()
const { data: projectDetails } = useSelectedProjectQuery()
@@ -169,5 +169,3 @@ const StorageMenu = () => {
>
)
}
-
-export default StorageMenu
diff --git a/apps/studio/components/interfaces/Storage/StorageMenuV2.tsx b/apps/studio/components/interfaces/Storage/StorageMenuV2.tsx
new file mode 100644
index 0000000000000..e5b8e8592efb3
--- /dev/null
+++ b/apps/studio/components/interfaces/Storage/StorageMenuV2.tsx
@@ -0,0 +1,30 @@
+import { useParams } from 'common'
+import Link from 'next/link'
+import { Menu } from 'ui'
+import { BUCKET_TYPES, BUCKET_TYPE_KEYS, DEFAULT_BUCKET_TYPE } from './Storage.constants'
+
+export const StorageMenuV2 = () => {
+ const { ref, bucketType } = useParams()
+ const selectedBucketType = bucketType || DEFAULT_BUCKET_TYPE
+
+ return (
+
+
+
Bucket Types} />
+
+ {BUCKET_TYPE_KEYS.map((bucketTypeKey) => {
+ const isSelected = selectedBucketType === bucketTypeKey
+ const config = BUCKET_TYPES[bucketTypeKey]
+
+ return (
+
+
+ {config.displayName}
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/apps/studio/components/interfaces/Support/SupportFormV2.tsx b/apps/studio/components/interfaces/Support/SupportFormV2.tsx
index 23f571d3a55b7..891d344b5f3ed 100644
--- a/apps/studio/components/interfaces/Support/SupportFormV2.tsx
+++ b/apps/studio/components/interfaces/Support/SupportFormV2.tsx
@@ -20,6 +20,7 @@ import { SubmitHandler, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import * as z from 'zod'
+import { SupportCategories } from '@supabase/shared-types/out/constants'
import { useDocsSearch, useParams, type DocsSearchResult as Page } from 'common'
import { CLIENT_LIBRARIES } from 'common/constants'
import CopyButton from 'components/ui/CopyButton'
@@ -29,6 +30,7 @@ import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { getProjectDetail } from 'data/projects/project-detail-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
+import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { DOCS_URL } from 'lib/constants'
import { detectBrowser } from 'lib/helpers'
import { useProfile } from 'lib/profile'
@@ -139,6 +141,21 @@ export const SupportFormV2 = ({
const router = useRouter()
const dashboardSentryIssueId = router.query.sid as string
+ const isBillingEnabled = useIsFeatureEnabled('billing:all')
+
+ const categoryOptions = useMemo(() => {
+ return CATEGORY_OPTIONS.filter((option) => {
+ if (
+ option.value === SupportCategories.BILLING ||
+ option.value === SupportCategories.REFUND ||
+ option.value === SupportCategories.SALES_ENQUIRY
+ ) {
+ return isBillingEnabled
+ }
+ return true
+ })
+ }, [isBillingEnabled])
+
const uploadButtonRef = useRef(null)
const [isSubmitting, setIsSubmitting] = useState(false)
const [docsResults, setDocsResults] = useState([])
@@ -317,7 +334,7 @@ export const SupportFormV2 = ({
useEffect(() => {
if (urlCategory) {
- const validCategory = CATEGORY_OPTIONS.find((option) => {
+ const validCategory = categoryOptions.find((option) => {
if (option.value.toLowerCase() === ((urlCategory as string) ?? '').toLowerCase())
return option
})
@@ -533,13 +550,13 @@ export const SupportFormV2 = ({
{field.value
- ? CATEGORY_OPTIONS.find((o) => o.value === field.value)?.label
+ ? categoryOptions.find((o) => o.value === field.value)?.label
: null}
- {CATEGORY_OPTIONS.map((option) => (
+ {categoryOptions.map((option) => (
{option.label}
diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx
index c08feee6cd642..2f0b698771ca5 100644
--- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx
@@ -41,10 +41,17 @@ export const generateToolRoutes = (ref?: string, project?: Project, features?: {
},
]
}
+
export const generateProductRoutes = (
ref?: string,
project?: Project,
- features?: { auth?: boolean; edgeFunctions?: boolean; storage?: boolean; realtime?: boolean }
+ features?: {
+ auth?: boolean
+ edgeFunctions?: boolean
+ storage?: boolean
+ realtime?: boolean
+ isStorageV2?: boolean
+ }
): Route[] => {
const isProjectActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY
const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP
@@ -54,6 +61,7 @@ export const generateProductRoutes = (
const edgeFunctionsEnabled = features?.edgeFunctions ?? true
const storageEnabled = features?.storage ?? true
const realtimeEnabled = features?.realtime ?? true
+ const isStorageV2 = features?.isStorageV2 ?? false
const databaseMenu = generateDatabaseMenu(project)
const authMenu = generateAuthMenu(ref as string)
@@ -89,7 +97,11 @@ export const generateProductRoutes = (
key: 'storage',
label: 'Storage',
icon: ,
- link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/storage/buckets`),
+ link:
+ ref &&
+ (isProjectBuilding
+ ? buildingUrl
+ : `/project/${ref}/storage/${isStorageV2 ? 'files' : 'buckets'}`),
},
]
: []),
diff --git a/apps/studio/components/layouts/ProjectLayout/OrganizationSettingsLayout.tsx b/apps/studio/components/layouts/ProjectLayout/OrganizationSettingsLayout.tsx
index d198aee651ec4..836ad3584ac9c 100644
--- a/apps/studio/components/layouts/ProjectLayout/OrganizationSettingsLayout.tsx
+++ b/apps/studio/components/layouts/ProjectLayout/OrganizationSettingsLayout.tsx
@@ -13,22 +13,28 @@ function OrganizationSettingsLayout({ children }: PropsWithChildren) {
const fullCurrentPath = useCurrentPath()
const [currentPath] = fullCurrentPath.split('#')
- const showSsoSettings = useIsFeatureEnabled('organization:show_sso_settings')
+ const {
+ organizationShowSsoSettings: showSsoSettings,
+ organizationShowSecuritySettings: showSecuritySettings,
+ } = useIsFeatureEnabled(['organization:show_sso_settings', 'organization:show_security_settings'])
const navMenuItems = [
{
label: 'General',
href: `/org/${slug}/general`,
},
- {
- label: 'Security',
- href: `/org/${slug}/security`,
- },
+ ...(showSecuritySettings
+ ? [
+ {
+ label: 'Security',
+ href: `/org/${slug}/security`,
+ },
+ ]
+ : []),
{
label: 'OAuth Apps',
href: `/org/${slug}/apps`,
},
-
...(showSsoSettings
? [
{
diff --git a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsLayout.tsx b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsLayout.tsx
index 7ab7becd7566d..52123de65e16c 100644
--- a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsLayout.tsx
+++ b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsLayout.tsx
@@ -35,6 +35,7 @@ const SettingsLayout = ({ title, children }: PropsWithChildren ,
},
diff --git a/apps/studio/components/layouts/StorageLayout/BucketLayout.tsx b/apps/studio/components/layouts/StorageLayout/BucketLayout.tsx
new file mode 100644
index 0000000000000..53d2a252e6f2c
--- /dev/null
+++ b/apps/studio/components/layouts/StorageLayout/BucketLayout.tsx
@@ -0,0 +1,30 @@
+import { PropsWithChildren } from 'react'
+
+import { useParams } from 'common'
+import { BUCKET_TYPES, DEFAULT_BUCKET_TYPE } from 'components/interfaces/Storage/Storage.constants'
+import { DocsButton } from 'components/ui/DocsButton'
+import DefaultLayout from '../DefaultLayout'
+import { PageLayout } from '../PageLayout/PageLayout'
+import { ScaffoldContainer } from '../Scaffold'
+import StorageLayout from './StorageLayout'
+
+export const BucketTypeLayout = ({ children }: PropsWithChildren) => {
+ const { bucketType } = useParams()
+ const bucketTypeKey = bucketType || DEFAULT_BUCKET_TYPE
+ const config = BUCKET_TYPES[bucketTypeKey as keyof typeof BUCKET_TYPES]
+ const secondaryActions = [ ]
+
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/apps/studio/components/layouts/StorageLayout/StorageLayout.tsx b/apps/studio/components/layouts/StorageLayout/StorageLayout.tsx
index 79c1898809f2e..101a5c519c9af 100644
--- a/apps/studio/components/layouts/StorageLayout/StorageLayout.tsx
+++ b/apps/studio/components/layouts/StorageLayout/StorageLayout.tsx
@@ -1,6 +1,8 @@
import { ReactNode } from 'react'
-import StorageMenu from 'components/interfaces/Storage/StorageMenu'
+import { useIsNewStorageUIEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
+import { StorageMenu } from 'components/interfaces/Storage/StorageMenu'
+import { StorageMenuV2 } from 'components/interfaces/Storage/StorageMenuV2'
import { withAuth } from 'hooks/misc/withAuth'
import ProjectLayout from '../ProjectLayout/ProjectLayout'
@@ -10,12 +12,14 @@ export interface StorageLayoutProps {
}
const StorageLayout = ({ title, children }: StorageLayoutProps) => {
+ const isStorageV2 = useIsNewStorageUIEnabled()
+
return (
}
+ productMenu={isStorageV2 ? : }
>
{children}
diff --git a/apps/studio/hooks/ui/useFlag.ts b/apps/studio/hooks/ui/useFlag.ts
index 979334882d3c0..3ad15f0cc5ede 100644
--- a/apps/studio/hooks/ui/useFlag.ts
+++ b/apps/studio/hooks/ui/useFlag.ts
@@ -1,8 +1,7 @@
import * as Sentry from '@sentry/nextjs'
-import { useFeatureFlags, useFlag } from 'common'
+import { useFeatureFlags } from 'common'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
-import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { trackFeatureFlag } from 'lib/posthog'
const isObjectEmpty = (obj: Object) => {
diff --git a/apps/studio/pages/org/[slug]/security.tsx b/apps/studio/pages/org/[slug]/security.tsx
index 4948e3e1d349d..0740972492481 100644
--- a/apps/studio/pages/org/[slug]/security.tsx
+++ b/apps/studio/pages/org/[slug]/security.tsx
@@ -1,18 +1,28 @@
+import { useParams } from 'common'
import { SecuritySettings } from 'components/interfaces/Organization/SecuritySettings/SecuritySettings'
import DefaultLayout from 'components/layouts/DefaultLayout'
import OrganizationLayout from 'components/layouts/OrganizationLayout'
import OrganizationSettingsLayout from 'components/layouts/ProjectLayout/OrganizationSettingsLayout'
+import { UnknownInterface } from 'components/ui/UnknownInterface'
+import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import type { NextPageWithLayout } from 'types'
-const OrgGeneralSettings: NextPageWithLayout = () => {
+const OrgSecuritySettings: NextPageWithLayout = () => {
+ const { slug } = useParams()
+ const showSecuritySettings = useIsFeatureEnabled('organization:show_security_settings')
+
+ if (!showSecuritySettings) {
+ return
+ }
+
return
}
-OrgGeneralSettings.getLayout = (page) => (
+OrgSecuritySettings.getLayout = (page) => (
{page}
)
-export default OrgGeneralSettings
+export default OrgSecuritySettings
diff --git a/apps/studio/pages/project/[ref]/storage/[bucketType]/index.tsx b/apps/studio/pages/project/[ref]/storage/[bucketType]/index.tsx
new file mode 100644
index 0000000000000..c762752bc478d
--- /dev/null
+++ b/apps/studio/pages/project/[ref]/storage/[bucketType]/index.tsx
@@ -0,0 +1,40 @@
+import { useRouter } from 'next/router'
+import { useEffect } from 'react'
+
+import { useParams } from 'common'
+import { useIsNewStorageUIEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
+import { BUCKET_TYPES, DEFAULT_BUCKET_TYPE } from 'components/interfaces/Storage/Storage.constants'
+import { BucketTypeLayout } from 'components/layouts/StorageLayout/BucketLayout'
+import type { NextPageWithLayout } from 'types'
+
+const BucketTypePage: NextPageWithLayout = () => {
+ const router = useRouter()
+ const { bucketType, ref } = useParams()
+ const isStorageV2 = useIsNewStorageUIEnabled()
+
+ const bucketTypeKey = bucketType || DEFAULT_BUCKET_TYPE
+ const config = BUCKET_TYPES[bucketTypeKey as keyof typeof BUCKET_TYPES]
+
+ useEffect(() => {
+ if (!isStorageV2) router.replace(`/project/${ref}/storage`)
+ }, [isStorageV2, ref])
+
+ useEffect(() => {
+ if (!config) {
+ router.replace(`/project/${ref}/storage`)
+ }
+ }, [config, ref, router])
+
+ return (
+
+ {/* [Danny] Purposefully duplicated directly below StorageLayout's config.description for now. Will be placed in a conditional empty state in next PR. TODO: consider reusing FormHeader for non-empty state.*/}
+ {/*
{config.description}
*/}
+
+ )
+}
+
+BucketTypePage.getLayout = (page) => {
+ return {page}
+}
+
+export default BucketTypePage
diff --git a/apps/studio/pages/project/[ref]/storage/buckets/index.tsx b/apps/studio/pages/project/[ref]/storage/buckets/index.tsx
index 4ca7587160f3e..6cefe0c843d33 100644
--- a/apps/studio/pages/project/[ref]/storage/buckets/index.tsx
+++ b/apps/studio/pages/project/[ref]/storage/buckets/index.tsx
@@ -1,5 +1,9 @@
import { useParams } from 'common'
+import { useRouter } from 'next/router'
+import { useEffect } from 'react'
+import { useIsNewStorageUIEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
+import { DEFAULT_BUCKET_TYPE } from 'components/interfaces/Storage/Storage.constants'
import StorageBucketsError from 'components/interfaces/Storage/StorageBucketsError'
import DefaultLayout from 'components/layouts/DefaultLayout'
import StorageLayout from 'components/layouts/StorageLayout/StorageLayout'
@@ -9,17 +13,22 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { DOCS_URL } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
-/**
- * PageLayout is used to setup layout - as usual it will requires inject global store
- */
const PageLayout: NextPageWithLayout = () => {
const { ref } = useParams()
+ const router = useRouter()
+ const isStorageV2 = useIsNewStorageUIEnabled()
const { data: project } = useSelectedProjectQuery()
const { error, isError } = useBucketsQuery({ projectRef: ref })
+ useEffect(() => {
+ if (isStorageV2) {
+ router.replace(`/project/${ref}/storage/${DEFAULT_BUCKET_TYPE}`)
+ }
+ }, [isStorageV2, ref, router])
+
if (!project) return null
- if (isError)
+ if (isError) return
return (
diff --git a/apps/studio/pages/project/[ref]/storage/index.tsx b/apps/studio/pages/project/[ref]/storage/index.tsx
index 153afc04db514..0b9672eb09d41 100644
--- a/apps/studio/pages/project/[ref]/storage/index.tsx
+++ b/apps/studio/pages/project/[ref]/storage/index.tsx
@@ -1,9 +1,27 @@
-import StorageLayout from 'components/layouts/StorageLayout/StorageLayout'
+import { useRouter } from 'next/router'
+import { useEffect } from 'react'
+
+import { useParams } from 'common'
+import { useIsNewStorageUIEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
+import { DEFAULT_BUCKET_TYPE } from 'components/interfaces/Storage/Storage.constants'
import DefaultLayout from 'components/layouts/DefaultLayout'
+import StorageLayout from 'components/layouts/StorageLayout/StorageLayout'
import type { NextPageWithLayout } from 'types'
const Storage: NextPageWithLayout = () => {
- return <>{/*
Use this as a template for storage pages */}>
+ const { ref } = useParams()
+ const router = useRouter()
+ const isStorageV2 = useIsNewStorageUIEnabled()
+
+ useEffect(() => {
+ if (isStorageV2) {
+ router.replace(`/project/${ref}/storage/${DEFAULT_BUCKET_TYPE}`)
+ } else {
+ router.replace(`/project/${ref}/storage/buckets`)
+ }
+ }, [isStorageV2, ref, router])
+
+ return null
}
Storage.getLayout = (page) => (
diff --git a/apps/studio/public/img/previews/new-storage-preview.png b/apps/studio/public/img/previews/new-storage-preview.png
new file mode 100644
index 0000000000000..18e6b9344d647
Binary files /dev/null and b/apps/studio/public/img/previews/new-storage-preview.png differ
diff --git a/packages/common/constants/local-storage.ts b/packages/common/constants/local-storage.ts
index 11e8d32ce6ad6..0ac015cb730d5 100644
--- a/packages/common/constants/local-storage.ts
+++ b/packages/common/constants/local-storage.ts
@@ -15,6 +15,7 @@ export const LOCAL_STORAGE_KEYS = {
UI_ONBOARDING_NEW_PAGE_SHOWN: 'supabase-ui-onboarding-new-page-shown',
UI_PREVIEW_BRANCHING_2_0: 'supabase-ui-branching-2-0',
UI_PREVIEW_ADVISOR_RULES: 'supabase-ui-advisor-rules',
+ UI_PREVIEW_NEW_STORAGE_UI: 'new-storage-ui',
NEW_LAYOUT_NOTICE_ACKNOWLEDGED: 'new-layout-notice-acknowledge',
TABS_INTERFACE_ACKNOWLEDGED: 'tabs-interface-acknowledge',
diff --git a/packages/common/enabled-features/enabled-features.json b/packages/common/enabled-features/enabled-features.json
index 996dc0790ac36..678d1ef756086 100644
--- a/packages/common/enabled-features/enabled-features.json
+++ b/packages/common/enabled-features/enabled-features.json
@@ -65,6 +65,7 @@
"logs:collections": true,
"organization:show_sso_settings": true,
+ "organization:show_security_settings": true,
"profile:show_email": true,
"profile:show_information": true,
diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json
index 21a69510b58ae..c0a812e7b504c 100644
--- a/packages/common/enabled-features/enabled-features.schema.json
+++ b/packages/common/enabled-features/enabled-features.schema.json
@@ -229,6 +229,10 @@
"type": "boolean",
"description": "Show the SSO settings tab in the organization settings page"
},
+ "organization:show_security_settings": {
+ "type": "boolean",
+ "description": "Show the security settings tab in the organization settings page"
+ },
"profile:show_email": {
"type": "boolean",