diff --git a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx index 4409bc24e5401..7c461c69a3ba8 100644 --- a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx @@ -14,6 +14,7 @@ import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -42,6 +43,8 @@ const schema = object({ const BasicAuthSettingsForm = () => { const { ref: projectRef } = useParams() + const showManualLinking = useIsFeatureEnabled('authentication:show_manual_linking') + const { data: authConfig, error: authConfigError, @@ -169,38 +172,40 @@ const BasicAuthSettingsForm = () => { )} /> - - ( - - Enable{' '} - - manual linking APIs - {' '} - for your project - - } - > - - - - - )} - /> - + {showManualLinking && ( + + ( + + Enable{' '} + + manual linking APIs + {' '} + for your project + + } + > + + + + + )} + /> + + )} { + const showStripeWrapper = useIsFeatureEnabled('integrations:show_stripe_wrapper') + const [selectedCategory, setSelectedCategory] = useQueryState( 'category', parseAsString.withDefault('all').withOptions({ clearOnDefault: true }) @@ -26,16 +29,26 @@ export const AvailableIntegrations = () => { parseAsString.withDefault('').withOptions({ clearOnDefault: true }) ) - const { availableIntegrations, installedIntegrations, error, isError, isLoading, isSuccess } = - useInstalledIntegrations() + const { + availableIntegrations: allIntegrations, + installedIntegrations, + error, + isError, + isLoading, + isSuccess, + } = useInstalledIntegrations() const installedIds = installedIntegrations.map((i) => i.id) // available integrations for install + const availableIntegrations = showStripeWrapper + ? allIntegrations + : allIntegrations.filter((x) => x.id !== 'stripe_wrapper') const integrationsByCategory = selectedCategory === 'all' ? availableIntegrations : availableIntegrations.filter((i) => i.type === selectedCategory) + const filteredIntegrations = ( search.length > 0 ? integrationsByCategory.filter((i) => i.name.toLowerCase().includes(search.toLowerCase())) diff --git a/apps/studio/components/interfaces/Settings/Addons/Addons.tsx b/apps/studio/components/interfaces/Settings/Addons/Addons.tsx index 50cba0c77a5b7..058c99f796b18 100644 --- a/apps/studio/components/interfaces/Settings/Addons/Addons.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/Addons.tsx @@ -28,6 +28,7 @@ import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import type { ProjectAddonVariantMeta } from 'data/subscriptions/types' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useIsOrioleDbInAws, @@ -45,13 +46,18 @@ import CustomDomainSidePanel from './CustomDomainSidePanel' import IPv4SidePanel from './IPv4SidePanel' import PITRSidePanel from './PITRSidePanel' -const Addons = () => { +export const Addons = () => { const { resolvedTheme } = useTheme() const { ref: projectRef } = useParams() const { setPanel } = useAddonsPagePanel() const isProjectActive = useIsProjectActive() const isOrioleDbInAws = useIsOrioleDbInAws() + const { projectSettingsCustomDomains, projectAddonsDedicatedIpv4Address } = useIsFeatureEnabled([ + 'project_settings:custom_domains', + 'project_addons:dedicated_ipv4_address', + ]) + const { data: selectedOrg } = useSelectedOrganizationQuery() const { data: selectedProject, isLoading: isLoadingProject } = useSelectedProjectQuery() const { data: parentProject } = useProjectByRefQuery(selectedProject?.parent_project_ref) @@ -330,80 +336,83 @@ const Addons = () => { - - - - - -
-

Dedicated IPv4 address

-
-

More information

-
- -
-

About IPv4 deprecation

- + {projectAddonsDedicatedIpv4Address && ( + <> + + + + +
+

Dedicated IPv4 address

+
+

More information

+
+ +
+

About IPv4 deprecation

+ +
+
- +
-
-
- - -
-
-
- IPv4 + + +
+
+
+ IPv4 +
+
+
+

Current option:

+

+ {ipv4 !== undefined + ? 'Dedicated IPv4 address is enabled' + : 'Dedicated IPv4 address is not enabled'} +

+ setPanel('ipv4')} + disabled={ + !isProjectActive || projectUpdateDisabled || !(canUpdateIPv4 || ipv4) + } + tooltip={{ + content: { + side: 'bottom', + text: !(canUpdateIPv4 || ipv4) + ? 'Temporarily disabled while we are migrating to IPv6, please check back later.' + : undefined, + }, + }} + > + Change dedicated IPv4 address + +
-
-
-

Current option:

-

- {ipv4 !== undefined - ? 'Dedicated IPv4 address is enabled' - : 'Dedicated IPv4 address is not enabled'} -

- setPanel('ipv4')} - disabled={ - !isProjectActive || projectUpdateDisabled || !(canUpdateIPv4 || ipv4) - } - tooltip={{ - content: { - side: 'bottom', - text: !(canUpdateIPv4 || ipv4) - ? 'Temporarily disabled while we are migrating to IPv6, please check back later.' - : undefined, - }, - }} - > - Change dedicated IPv4 address - -
-
- - - + + + + + )} @@ -528,75 +537,78 @@ const Addons = () => { - - - - - -
-

Custom domain

-
-

More information

-
- -
-

About custom domains

- + {projectSettingsCustomDomains && ( + <> + + + + +
+

Custom domain

+
+

More information

+
+ +
+

About custom domains

+ +
+
- +
-
-
- - -
-
-
- Custom Domain + + +
+
+
+ Custom Domain +
+
+
+

Current option:

+

+ {customDomain !== undefined + ? 'Custom domain is enabled' + : 'Custom domain is not enabled'} +

+ + + +
-
-
-

Current option:

-

- {customDomain !== undefined - ? 'Custom domain is enabled' - : 'Custom domain is not enabled'} -

- - - -
-
- - - + + + + + )} )} @@ -606,5 +618,3 @@ const Addons = () => { ) } - -export default Addons diff --git a/apps/studio/components/interfaces/Settings/Database/ConnectionStringMoved.tsx b/apps/studio/components/interfaces/Settings/Database/ConnectionStringMoved.tsx deleted file mode 100644 index 3852592b86259..0000000000000 --- a/apps/studio/components/interfaces/Settings/Database/ConnectionStringMoved.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Button } from 'ui' -import { Plug, GitBranch, ChevronsUpDown, Pointer } from 'lucide-react' - -export const ConnectionStringMoved = () => { - return ( -
-
-

Connection string has moved

-

- You can find Project connect details by clicking 'Connect' in the top bar -

-
-
-
-
-
-
-
- - - -
- -
-
-
-
- ) -} diff --git a/apps/studio/components/interfaces/Sidebar.tsx b/apps/studio/components/interfaces/Sidebar.tsx index efda42e425262..492a995655664 100644 --- a/apps/studio/components/interfaces/Sidebar.tsx +++ b/apps/studio/components/interfaces/Sidebar.tsx @@ -224,6 +224,7 @@ const ProjectLinks = () => { const snap = useAppStateSnapshot() const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled() const { securityLints, errorLints } = useLints() + const showReports = useIsFeatureEnabled('reports:all') const activeRoute = router.pathname.split('/')[3] @@ -251,6 +252,7 @@ const ProjectLinks = () => { const otherRoutes = generateOtherRoutes(ref, project, { unifiedLogs: isUnifiedLogsEnabled, + showReports, }) const settingsRoutes = generateSettingsRoutes(ref, project) diff --git a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx index 0d6d2f670617e..c4dab21d894c7 100644 --- a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx @@ -65,8 +65,7 @@ const GridHeaderActions = ({ table }: GridHeaderActionsProps) => { const isView = isTableLikeView(table) const isMaterializedView = isTableLikeMaterializedView(table) - const { realtimeAll: realtimeEnabled, tableEditorEnableRlsToggle: enableRlsToggle } = - useIsFeatureEnabled(['realtime:all', 'table_editor:enable_rls_toggle']) + const { realtimeAll: realtimeEnabled } = useIsFeatureEnabled(['realtime:all']) const { isSchemaLocked } = useIsProtectedSchema({ schema: table.schema }) const { mutate: updateTable } = useTableUpdateMutation({ @@ -212,7 +211,7 @@ const GridHeaderActions = ({ table }: GridHeaderActionsProps) => { )} - {enableRlsToggle && isTable && !isSchemaLocked ? ( + {isTable && !isSchemaLocked ? ( table.rls_enabled ? ( <> {policies.length < 1 && !isSchemaLocked ? ( diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index 369adc2673a4b..c9ca072bd52cb 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -48,7 +48,7 @@ import { updateTable, } from './SidePanelEditor.utils' import SpreadsheetImport from './SpreadsheetImport/SpreadsheetImport' -import TableEditor from './TableEditor/TableEditor' +import { TableEditor } from './TableEditor/TableEditor' import type { ImportContent } from './TableEditor/TableEditor.types' export interface SidePanelEditorProps { diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index b62381c2a5b95..cb4f1d26eebd8 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -72,7 +72,7 @@ export interface TableEditorProps { updateEditorDirty: () => void } -const TableEditor = ({ +export const TableEditor = ({ table, isDuplicating, visible = false, @@ -85,8 +85,7 @@ const TableEditor = ({ const { data: org } = useSelectedOrganizationQuery() const { selectedSchema } = useQuerySchemaState() const isNewRecord = isUndefined(table) - const { realtimeAll: realtimeEnabled, tableEditorEnableRlsToggle: enableRlsToggle } = - useIsFeatureEnabled(['realtime:all', 'table_editor:enable_rls_toggle']) + const { realtimeAll: realtimeEnabled } = useIsFeatureEnabled(['realtime:all']) const { mutate: sendEvent } = useSendEventMutation() const [params, setParams] = useUrlState() @@ -293,69 +292,67 @@ const TableEditor = ({ - {enableRlsToggle && ( - <> - - Enable Row Level Security (RLS) - Recommended -
- } - description="Restrict access to your table by enabling RLS and writing Postgres policies." - checked={tableFields.isRLSEnabled} - onChange={() => { - // if isEnabled, show confirm modal to turn off - // if not enabled, allow turning on without modal confirmation - tableFields.isRLSEnabled - ? setRlsConfirmVisible(true) - : onUpdateField({ isRLSEnabled: !tableFields.isRLSEnabled }) - }} - size="medium" + + Enable Row Level Security (RLS) + Recommended +
+ } + description="Restrict access to your table by enabling RLS and writing Postgres policies." + checked={tableFields.isRLSEnabled} + onChange={() => { + // if isEnabled, show confirm modal to turn off + // if not enabled, allow turning on without modal confirmation + tableFields.isRLSEnabled + ? setRlsConfirmVisible(true) + : onUpdateField({ isRLSEnabled: !tableFields.isRLSEnabled }) + }} + size="medium" + /> + + {tableFields.isRLSEnabled ? ( + + You need to create an access policy before you can query data from this table. + Without a policy, querying this table will return an{' '} + empty array of results.{' '} + {isNewRecord ? 'You can create policies after saving this table.' : ''} + + } + > + - {tableFields.isRLSEnabled ? ( - - You need to create an access policy before you can query data from this table. - Without a policy, querying this table will return an{' '} - empty array of results.{' '} - {isNewRecord ? 'You can create policies after saving this table.' : ''} - - } - > - - - ) : ( - - {tableFields.name ? `The table ${tableFields.name}` : 'Your table'} will be - publicly writable and readable - - } - > - - - )} - + + ) : ( + + {tableFields.name ? `The table ${tableFields.name}` : 'Your table'} will be publicly + writable and readable + + } + > + + )} + {realtimeEnabled && ( )} + + {!isDuplicating && ( ) } - -export default TableEditor diff --git a/apps/studio/components/layouts/AuthLayout/AuthEmailsLayout.tsx b/apps/studio/components/layouts/AuthLayout/AuthEmailsLayout.tsx index 82ee2c1ecd412..fe69585fe33a0 100644 --- a/apps/studio/components/layouts/AuthLayout/AuthEmailsLayout.tsx +++ b/apps/studio/components/layouts/AuthLayout/AuthEmailsLayout.tsx @@ -2,10 +2,13 @@ import { PropsWithChildren } from 'react' import { useParams } from 'common' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' +import { UnknownInterface } from 'components/ui/UnknownInterface' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import AuthLayout from './AuthLayout' export const AuthEmailsLayout = ({ children }: PropsWithChildren<{}>) => { const { ref } = useParams() + const showEmails = useIsFeatureEnabled('authentication:emails') const navItems = [ { @@ -20,13 +23,17 @@ export const AuthEmailsLayout = ({ children }: PropsWithChildren<{}>) => { return ( - - {children} - + {showEmails ? ( + + {children} + + ) : ( + + )} ) } diff --git a/apps/studio/components/layouts/AuthLayout/AuthLayout.tsx b/apps/studio/components/layouts/AuthLayout/AuthLayout.tsx index 002d48acbd82f..a57815f65d470 100644 --- a/apps/studio/components/layouts/AuthLayout/AuthLayout.tsx +++ b/apps/studio/components/layouts/AuthLayout/AuthLayout.tsx @@ -4,6 +4,7 @@ import { PropsWithChildren } from 'react' import { useParams } from 'common' import { ProductMenu } from 'components/ui/ProductMenu' import { useAuthConfigPrefetch } from 'data/auth/auth-config-query' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { withAuth } from 'hooks/misc/withAuth' import ProjectLayout from '../ProjectLayout/ProjectLayout' import { generateAuthMenu } from './AuthLayout.utils' @@ -12,10 +13,38 @@ const AuthProductMenu = () => { const router = useRouter() const { ref: projectRef = 'default' } = useParams() + const { + authenticationSignInProviders, + authenticationRateLimits, + authenticationEmails, + authenticationMultiFactor, + authenticationAttackProtection, + authenticationAdvanced, + } = useIsFeatureEnabled([ + 'authentication:sign_in_providers', + 'authentication:rate_limits', + 'authentication:emails', + 'authentication:multi_factor', + 'authentication:attack_protection', + 'authentication:advanced', + ]) + useAuthConfigPrefetch({ projectRef }) const page = router.pathname.split('/')[4] - return + return ( + + ) } const AuthLayout = ({ children }: PropsWithChildren<{}>) => { diff --git a/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.ts b/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.ts index f3eba231bfbfa..8bf8dd7da55b6 100644 --- a/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.ts +++ b/apps/studio/components/layouts/AuthLayout/AuthLayout.utils.ts @@ -1,7 +1,26 @@ import type { ProductMenuGroup } from 'components/ui/ProductMenu/ProductMenu.types' import { IS_PLATFORM } from 'lib/constants' -export const generateAuthMenu = (ref: string): ProductMenuGroup[] => { +export const generateAuthMenu = ( + ref: string, + flags?: { + authenticationSignInProviders: boolean + authenticationRateLimits: boolean + authenticationEmails: boolean + authenticationMultiFactor: boolean + authenticationAttackProtection: boolean + authenticationAdvanced: boolean + } +): ProductMenuGroup[] => { + const { + authenticationSignInProviders, + authenticationRateLimits, + authenticationEmails, + authenticationMultiFactor, + authenticationAttackProtection, + authenticationAdvanced, + } = flags ?? {} + return [ { title: 'Manage', @@ -18,50 +37,70 @@ export const generateAuthMenu = (ref: string): ProductMenuGroup[] => { }, ...(IS_PLATFORM ? [ - { - name: 'Sign In / Providers', - key: 'sign-in-up', - pages: ['providers', 'third-party'], - url: `/project/${ref}/auth/providers`, - items: [], - }, + ...(authenticationSignInProviders + ? [ + { + name: 'Sign In / Providers', + key: 'sign-in-up', + pages: ['providers', 'third-party'], + url: `/project/${ref}/auth/providers`, + items: [], + }, + ] + : []), { name: 'Sessions', key: 'sessions', url: `/project/${ref}/auth/sessions`, items: [], }, - { - name: 'Rate Limits', - key: 'rate-limits', - url: `/project/${ref}/auth/rate-limits`, - items: [], - }, - { - name: 'Emails', - key: 'emails', - pages: ['templates', 'smtp'], - url: `/project/${ref}/auth/templates`, - items: [], - }, - { - name: 'Multi-Factor', - key: 'mfa', - url: `/project/${ref}/auth/mfa`, - items: [], - }, + ...(authenticationRateLimits + ? [ + { + name: 'Rate Limits', + key: 'rate-limits', + url: `/project/${ref}/auth/rate-limits`, + items: [], + }, + ] + : []), + ...(authenticationEmails + ? [ + { + name: 'Emails', + key: 'emails', + pages: ['templates', 'smtp'], + url: `/project/${ref}/auth/templates`, + items: [], + }, + ] + : []), + ...(authenticationMultiFactor + ? [ + { + name: 'Multi-Factor', + key: 'mfa', + url: `/project/${ref}/auth/mfa`, + items: [], + }, + ] + : []), { name: 'URL Configuration', key: 'url-configuration', url: `/project/${ref}/auth/url-configuration`, items: [], }, - { - name: 'Attack Protection', - key: 'protection', - url: `/project/${ref}/auth/protection`, - items: [], - }, + ...(authenticationAttackProtection + ? [ + { + name: 'Attack Protection', + key: 'protection', + url: `/project/${ref}/auth/protection`, + items: [], + }, + ] + : []), { name: 'Auth Hooks', key: 'hooks', @@ -69,12 +108,16 @@ export const generateAuthMenu = (ref: string): ProductMenuGroup[] => { items: [], label: 'BETA', }, - { - name: 'Advanced', - key: 'advanced', - url: `/project/${ref}/auth/advanced`, - items: [], - }, + ...(authenticationAdvanced + ? [ + { + name: 'Advanced', + key: 'advanced', + url: `/project/${ref}/auth/advanced`, + items: [], + }, + ] + : []), ] : []), ], diff --git a/apps/studio/components/layouts/AuthLayout/AuthProvidersLayout.tsx b/apps/studio/components/layouts/AuthLayout/AuthProvidersLayout.tsx index 712b4c2fd8de3..f2a247ab280f0 100644 --- a/apps/studio/components/layouts/AuthLayout/AuthProvidersLayout.tsx +++ b/apps/studio/components/layouts/AuthLayout/AuthProvidersLayout.tsx @@ -2,31 +2,45 @@ import { PropsWithChildren } from 'react' import { useParams } from 'common' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' +import { UnknownInterface } from 'components/ui/UnknownInterface' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import AuthLayout from './AuthLayout' export const AuthProvidersLayout = ({ children }: PropsWithChildren<{}>) => { const { ref } = useParams() + const { authenticationSignInProviders, authenticationThirdPartyAuth } = useIsFeatureEnabled([ + 'authentication:sign_in_providers', + 'authentication:third_party_auth', + ]) const navItems = [ { label: 'Supabase Auth', href: `/project/${ref}/auth/providers`, }, - { - label: 'Third Party Auth', - href: `/project/${ref}/auth/third-party`, - }, + ...(authenticationThirdPartyAuth + ? [ + { + label: 'Third Party Auth', + href: `/project/${ref}/auth/third-party`, + }, + ] + : []), ] return ( - - {children} - + {authenticationSignInProviders ? ( + + {children} + + ) : ( + + )} ) } diff --git a/apps/studio/components/layouts/JWTKeys/JWTKeysLayout.tsx b/apps/studio/components/layouts/JWTKeys/JWTKeysLayout.tsx index acebf3386e59b..03ea980a382df 100644 --- a/apps/studio/components/layouts/JWTKeys/JWTKeysLayout.tsx +++ b/apps/studio/components/layouts/JWTKeys/JWTKeysLayout.tsx @@ -3,16 +3,22 @@ import { ScaffoldContainer } from 'components/layouts/Scaffold' import { PropsWithChildren } from 'react' import { useParams } from 'common' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' const JWTKeysLayout = ({ children }: PropsWithChildren) => { const { ref: projectRef } = useParams() + const { projectSettingsLegacyJwtKeys } = useIsFeatureEnabled(['project_settings:legacy_jwt_keys']) const navigationItems = [ - { - label: 'Legacy JWT Secret', - href: `/project/${projectRef}/settings/jwt`, - id: 'legacy-jwt-keys', - }, + ...(projectSettingsLegacyJwtKeys + ? [ + { + label: 'Legacy JWT Secret', + href: `/project/${projectRef}/settings/jwt`, + id: 'legacy-jwt-keys', + }, + ] + : []), { label: 'JWT Signing Keys', href: `/project/${projectRef}/settings/jwt/signing-keys`, diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx index 503d3c8d97329..c08feee6cd642 100644 --- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx +++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx @@ -119,12 +119,14 @@ export const generateProductRoutes = ( export const generateOtherRoutes = ( ref?: string, project?: Project, - features?: { unifiedLogs?: boolean } + features?: { unifiedLogs?: boolean; showReports?: boolean } ): Route[] => { const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP const buildingUrl = `/project/${ref}` - const unifiedLogsEnabled = features?.unifiedLogs ?? false + const { unifiedLogs, showReports } = features ?? {} + const unifiedLogsEnabled = unifiedLogs ?? false + const reportsEnabled = showReports ?? true return [ { @@ -133,7 +135,7 @@ export const generateOtherRoutes = ( icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/advisors/security`), }, - ...(IS_PLATFORM + ...(IS_PLATFORM && reportsEnabled ? [ { key: 'reports', diff --git a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsLayout.tsx b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsLayout.tsx index b3a56eb2f7b0d..7ab7becd7566d 100644 --- a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsLayout.tsx +++ b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsLayout.tsx @@ -38,11 +38,17 @@ const SettingsLayout = ({ title, children }: PropsWithChildren { const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP @@ -22,6 +25,9 @@ export const generateSettingsMenu = ( const authEnabled = features?.auth ?? true const edgeFunctionsEnabled = features?.edgeFunctions ?? true const storageEnabled = features?.storage ?? true + const legacyJwtKeysEnabled = features?.legacyJwtKeys ?? true + const logDrainsEnabled = features?.logDrains ?? true + const billingEnabled = features?.billing ?? true return [ { @@ -57,12 +63,16 @@ export const generateSettingsMenu = ( url: `/project/${ref}/settings/integrations`, items: [], }, - { - name: `Log Drains`, - key: `log-drains`, - url: `/project/${ref}/settings/log-drains`, - items: [], - }, + ...(logDrainsEnabled + ? [ + { + name: `Log Drains`, + key: `log-drains`, + url: `/project/${ref}/settings/log-drains`, + items: [], + }, + ] + : []), { name: 'Data API', key: 'api', @@ -79,7 +89,9 @@ export const generateSettingsMenu = ( { name: 'JWT Keys', key: 'jwt', - url: `/project/${ref}/settings/jwt`, + url: legacyJwtKeysEnabled + ? `/project/${ref}/settings/jwt` + : `/project/${ref}/settings/jwt/signing-keys`, items: [], label: 'NEW', }, @@ -116,8 +128,9 @@ export const generateSettingsMenu = ( { name: 'Authentication', key: 'auth', - url: isProjectBuilding ? buildingUrl : `/project/${ref}/settings/auth`, + url: `/project/${ref}/auth/policies`, items: [], + rightIcon: , }, ] : []), @@ -149,14 +162,17 @@ export const generateSettingsMenu = ( { title: 'Billing', items: [ - { - name: 'Subscription', - key: 'subscription', - url: `/org/${organization?.slug}/billing`, - items: [], - rightIcon: , - }, - + ...(billingEnabled + ? [ + { + name: 'Subscription', + key: 'subscription', + url: `/org/${organization?.slug}/billing`, + items: [], + rightIcon: , + }, + ] + : []), { name: 'Usage', key: 'usage', diff --git a/apps/studio/components/layouts/RealtimeLayout/RealtimeMenu.utils.ts b/apps/studio/components/layouts/RealtimeLayout/RealtimeMenu.utils.ts index 365c424d57a3b..1aa33aeb64ae7 100644 --- a/apps/studio/components/layouts/RealtimeLayout/RealtimeMenu.utils.ts +++ b/apps/studio/components/layouts/RealtimeLayout/RealtimeMenu.utils.ts @@ -8,6 +8,7 @@ export const generateRealtimeMenu = ( ): ProductMenuGroup[] => { const ref = project?.ref ?? 'default' const { enableRealtimeSettings } = flags || {} + const showRealtimeSettings = IS_PLATFORM && enableRealtimeSettings return [ { @@ -30,7 +31,7 @@ export const generateRealtimeMenu = ( url: `/project/${ref}/realtime/policies`, items: [], }, - ...(IS_PLATFORM && enableRealtimeSettings + ...(showRealtimeSettings ? [ { name: 'Settings', diff --git a/apps/studio/components/layouts/ReportsLayout/ReportsLayout.tsx b/apps/studio/components/layouts/ReportsLayout/ReportsLayout.tsx index c9cb77cc689bb..36bcf2cceebe2 100644 --- a/apps/studio/components/layouts/ReportsLayout/ReportsLayout.tsx +++ b/apps/studio/components/layouts/ReportsLayout/ReportsLayout.tsx @@ -1,5 +1,8 @@ import { PropsWithChildren } from 'react' +import { useParams } from 'common' +import { UnknownInterface } from 'components/ui/UnknownInterface' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { withAuth } from 'hooks/misc/withAuth' import ProjectLayout from '../ProjectLayout/ProjectLayout' import ReportsMenu from './ReportsMenu' @@ -9,11 +12,23 @@ interface ReportsLayoutProps { } const ReportsLayout = ({ title, children }: PropsWithChildren) => { - return ( - } isBlocking={false}> - {children} - - ) + const { ref } = useParams() + const { reportsAll } = useIsFeatureEnabled(['reports:all']) + + if (reportsAll) { + return ( + } + isBlocking={false} + > + {children} + + ) + } else { + return + } } export default withAuth(ReportsLayout) diff --git a/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx b/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx index f06425388fbcf..58c954f9a091d 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx @@ -97,7 +97,7 @@ const AssistantChatFormComponent = forwardRef( ref={formRef} {...props} onSubmit={handleSubmit} - className={cn('relative overflow-hidden', className)} + className={cn('relative', className)} > {sqlSnippets && sqlSnippets.length > 0 && ( { const { ref: projectRef } = useParams() + const { projectSettingsShowDisableLegacyApiKeys } = useIsFeatureEnabled([ + 'project_settings:show_disable_legacy_api_keys', + ]) const { data: settings, @@ -213,21 +217,22 @@ export const DisplayApiSettings = ({ )) )} - {showNotice ? ( - + + {showNotice ? ( + - ) : ( - - )} - + href="https://github.com/orgs/supabase/discussions/29260" + buttonText="Read the announcement" + /> + ) : projectSettingsShowDisableLegacyApiKeys ? ( + + ) : null} ) } diff --git a/apps/studio/pages/account/me.tsx b/apps/studio/pages/account/me.tsx index 6c7fed647c901..21c23c2af6461 100644 --- a/apps/studio/pages/account/me.tsx +++ b/apps/studio/pages/account/me.tsx @@ -9,17 +9,17 @@ import { AccountSettingsLayout } from 'components/layouts/AccountLayout/AccountS import AppLayout from 'components/layouts/AppLayout/AppLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import OrganizationLayout from 'components/layouts/OrganizationLayout' +import { + ScaffoldContainer, + ScaffoldHeader, + ScaffoldSectionTitle, +} from 'components/layouts/Scaffold' import AlertError from 'components/ui/AlertError' import Panel from 'components/ui/Panel' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useProfile } from 'lib/profile' import type { NextPageWithLayout } from 'types' -import { - ScaffoldContainer, - ScaffoldHeader, - ScaffoldSectionTitle, -} from 'components/layouts/Scaffold' const User: NextPageWithLayout = () => { return @@ -40,7 +40,12 @@ User.getLayout = (page) => ( export default User const ProfileCard = () => { - const profileShowInformation = useIsFeatureEnabled('profile:show_information') + const { profileShowInformation, profileShowAnalyticsAndMarketing, profileShowAccountDeletion } = + useIsFeatureEnabled([ + 'profile:show_information', + 'profile:show_analytics_and_marketing', + 'profile:show_account_deletion', + ]) const { error, isLoading, isError, isSuccess } = useProfile() return ( @@ -81,13 +86,17 @@ const ProfileCard = () => { -
- -
+ {profileShowAnalyticsAndMarketing && ( +
+ +
+ )} -
- -
+ {profileShowAccountDeletion && ( +
+ +
+ )} diff --git a/apps/studio/pages/project/[ref]/auth/advanced.tsx b/apps/studio/pages/project/[ref]/auth/advanced.tsx index 3bf49e11668a1..1524a38b69040 100644 --- a/apps/studio/pages/project/[ref]/auth/advanced.tsx +++ b/apps/studio/pages/project/[ref]/auth/advanced.tsx @@ -1,43 +1,51 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useParams } from 'common' import { AdvancedAuthSettingsForm } from 'components/interfaces/Auth/AdvancedAuthSettingsForm' import AuthLayout from 'components/layouts/AuthLayout/AuthLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' -import { ScaffoldContainer } from 'components/layouts/Scaffold' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' +import { UnknownInterface } from 'components/ui/UnknownInterface' import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const AdvancedPage: NextPageWithLayout = () => { + const { ref } = useParams() + const showAdvanced = useIsFeatureEnabled('authentication:advanced') + const isPermissionsLoaded = usePermissionsLoaded() const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - if (isPermissionsLoaded && !canReadAuthSettings) { - return + if (!showAdvanced) { + return } return ( - - {!isPermissionsLoaded ? ( -
- -
+ + {isPermissionsLoaded && !canReadAuthSettings ? ( + ) : ( - + + {!isPermissionsLoaded ? ( + + + + ) : ( + + )} + )} -
+ ) } AdvancedPage.getLayout = (page) => ( - - - {page} - - + {page} ) diff --git a/apps/studio/pages/project/[ref]/auth/mfa.tsx b/apps/studio/pages/project/[ref]/auth/mfa.tsx index cbe50b1ff0eb3..84d755116551e 100644 --- a/apps/studio/pages/project/[ref]/auth/mfa.tsx +++ b/apps/studio/pages/project/[ref]/auth/mfa.tsx @@ -1,40 +1,54 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useParams } from 'common' import { MfaAuthSettingsForm } from 'components/interfaces/Auth' import AuthLayout from 'components/layouts/AuthLayout/AuthLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' -import { ScaffoldContainer } from 'components/layouts/Scaffold' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' +import { UnknownInterface } from 'components/ui/UnknownInterface' import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const MfaPage: NextPageWithLayout = () => { + const { ref } = useParams() + const showMFA = useIsFeatureEnabled('authentication:multi_factor') + const isPermissionsLoaded = usePermissionsLoaded() const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - if (isPermissionsLoaded && !canReadAuthSettings) { - return + if (!showMFA) { + return } return ( - - {!isPermissionsLoaded ? : } - + + {isPermissionsLoaded && !canReadAuthSettings ? ( + + ) : ( + + {!isPermissionsLoaded ? ( + + + + ) : ( + + )} + + )} + ) } MfaPage.getLayout = (page) => ( - - - {page} - - + {page} ) diff --git a/apps/studio/pages/project/[ref]/auth/policies.tsx b/apps/studio/pages/project/[ref]/auth/policies.tsx index fe8f6313386cc..f379c6d457a9c 100644 --- a/apps/studio/pages/project/[ref]/auth/policies.tsx +++ b/apps/studio/pages/project/[ref]/auth/policies.tsx @@ -3,6 +3,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { Search } from 'lucide-react' import { useState } from 'react' +import { useParams } from 'common' import { useIsInlineEditorEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import Policies from 'components/interfaces/Auth/Policies/Policies' import { getGeneralPolicyTemplates } from 'components/interfaces/Auth/Policies/PolicyEditorModal/PolicyEditorModal.constants' @@ -61,6 +62,7 @@ const onFilterTables = ( } const AuthPoliciesPage: NextPageWithLayout = () => { + const { ref } = useParams() const [params, setParams] = useUrlState<{ schema?: string search?: string diff --git a/apps/studio/pages/project/[ref]/auth/protection.tsx b/apps/studio/pages/project/[ref]/auth/protection.tsx index 9751d097520fc..2e621551171b6 100644 --- a/apps/studio/pages/project/[ref]/auth/protection.tsx +++ b/apps/studio/pages/project/[ref]/auth/protection.tsx @@ -1,46 +1,54 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useParams } from 'common' import { ProtectionAuthSettingsForm } from 'components/interfaces/Auth' import AuthLayout from 'components/layouts/AuthLayout/AuthLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' -import { ScaffoldContainer } from 'components/layouts/Scaffold' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' +import { UnknownInterface } from 'components/ui/UnknownInterface' import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const ProtectionPage: NextPageWithLayout = () => { + const { ref } = useParams() + const showAttackProtection = useIsFeatureEnabled('authentication:attack_protection') + const isPermissionsLoaded = usePermissionsLoaded() const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - if (isPermissionsLoaded && !canReadAuthSettings) { - return + if (!showAttackProtection) { + return } return ( - - {!isPermissionsLoaded ? ( -
- -
+ + {isPermissionsLoaded && !canReadAuthSettings ? ( + ) : ( - + + {!isPermissionsLoaded ? ( + + + + ) : ( + + )} + )} -
+ ) } ProtectionPage.getLayout = (page) => ( - - - {page} - - + {page} ) diff --git a/apps/studio/pages/project/[ref]/auth/providers.tsx b/apps/studio/pages/project/[ref]/auth/providers.tsx index 2bdc9ab95848b..8c3db26924e5a 100644 --- a/apps/studio/pages/project/[ref]/auth/providers.tsx +++ b/apps/studio/pages/project/[ref]/auth/providers.tsx @@ -2,13 +2,16 @@ import { AuthProvidersForm, BasicAuthSettingsForm } from 'components/interfaces/ import { AuthProvidersLayout } from 'components/layouts/AuthLayout/AuthProvidersLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import { ScaffoldContainer } from 'components/layouts/Scaffold' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const ProvidersPage: NextPageWithLayout = () => { + const showProviders = useIsFeatureEnabled('authentication:show_providers') + return ( - + {showProviders && } ) } diff --git a/apps/studio/pages/project/[ref]/auth/rate-limits.tsx b/apps/studio/pages/project/[ref]/auth/rate-limits.tsx index 759ac70c1883d..185c000bd4ccc 100644 --- a/apps/studio/pages/project/[ref]/auth/rate-limits.tsx +++ b/apps/studio/pages/project/[ref]/auth/rate-limits.tsx @@ -1,44 +1,58 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useParams } from 'common' import RateLimits from 'components/interfaces/Auth/RateLimits/RateLimits' import AuthLayout from 'components/layouts/AuthLayout/AuthLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' -import { ScaffoldContainer } from 'components/layouts/Scaffold' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import { DocsButton } from 'components/ui/DocsButton' import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' +import { UnknownInterface } from 'components/ui/UnknownInterface' import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const RateLimitsPage: NextPageWithLayout = () => { + const { ref } = useParams() + const showRateLimits = useIsFeatureEnabled('authentication:rate_limits') + const isPermissionsLoaded = usePermissionsLoaded() const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - if (isPermissionsLoaded && !canReadAuthSettings) { - return + if (!showRateLimits) { + return } return ( - - {!isPermissionsLoaded ? : } - + + } + > + {isPermissionsLoaded && !canReadAuthSettings ? ( + + ) : ( + + {!isPermissionsLoaded ? ( + + + + ) : ( + + )} + + )} + ) } RateLimitsPage.getLayout = (page) => ( - - - } - > - {page} - - + {page} ) diff --git a/apps/studio/pages/project/[ref]/auth/third-party.tsx b/apps/studio/pages/project/[ref]/auth/third-party.tsx index e3d955abd2d1e..fdfbcc79f4c70 100644 --- a/apps/studio/pages/project/[ref]/auth/third-party.tsx +++ b/apps/studio/pages/project/[ref]/auth/third-party.tsx @@ -1,32 +1,45 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useParams } from 'common' import { ThirdPartyAuthForm } from 'components/interfaces/Auth' +import AuthLayout from 'components/layouts/AuthLayout/AuthLayout' import { AuthProvidersLayout } from 'components/layouts/AuthLayout/AuthProvidersLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import { ScaffoldContainer } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' +import { UnknownInterface } from 'components/ui/UnknownInterface' import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const ThirdPartyPage: NextPageWithLayout = () => { + const { ref } = useParams() const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') const isPermissionsLoaded = usePermissionsLoaded() - if (isPermissionsLoaded && !canReadAuthSettings) { - return + const showThirdPartyAuth = useIsFeatureEnabled('authentication:third_party_auth') + + if (!showThirdPartyAuth) { + return ( + + + + ) } return ( - - - + + {isPermissionsLoaded && !canReadAuthSettings ? ( + + ) : ( + + + + )} + ) } -ThirdPartyPage.getLayout = (page) => ( - - {page} - -) +ThirdPartyPage.getLayout = (page) => {page} export default ThirdPartyPage diff --git a/apps/studio/pages/project/[ref]/database/settings.tsx b/apps/studio/pages/project/[ref]/database/settings.tsx index a44f04db2d4a6..920d10470bd65 100644 --- a/apps/studio/pages/project/[ref]/database/settings.tsx +++ b/apps/studio/pages/project/[ref]/database/settings.tsx @@ -1,14 +1,13 @@ import { DiskManagementPanelForm } from 'components/interfaces/DiskManagement/DiskManagementPanelForm' import { ConnectionPooling, NetworkRestrictions } from 'components/interfaces/Settings/Database' import BannedIPs from 'components/interfaces/Settings/Database/BannedIPs' -import { ConnectionStringMoved } from 'components/interfaces/Settings/Database/ConnectionStringMoved' import { DatabaseReadOnlyAlert } from 'components/interfaces/Settings/Database/DatabaseReadOnlyAlert' import ResetDbPassword from 'components/interfaces/Settings/Database/DatabaseSettings/ResetDbPassword' import DiskSizeConfiguration from 'components/interfaces/Settings/Database/DiskSizeConfiguration' import { PoolingModesModal } from 'components/interfaces/Settings/Database/PoolingModesModal' import SSLConfiguration from 'components/interfaces/Settings/Database/SSLConfiguration' -import DefaultLayout from 'components/layouts/DefaultLayout' import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout' +import DefaultLayout from 'components/layouts/DefaultLayout' import { ScaffoldContainer, ScaffoldHeader, ScaffoldTitle } from 'components/layouts/Scaffold' import { useIsAwsCloudProvider, useIsAwsK8sCloudProvider } from 'hooks/misc/useSelectedProject' import type { NextPageWithLayout } from 'types' @@ -30,7 +29,6 @@ const ProjectSettings: NextPageWithLayout = () => {
-
diff --git a/apps/studio/pages/project/[ref]/realtime/policies.tsx b/apps/studio/pages/project/[ref]/realtime/policies.tsx index 20555d9d1340d..028d3990d2e4b 100644 --- a/apps/studio/pages/project/[ref]/realtime/policies.tsx +++ b/apps/studio/pages/project/[ref]/realtime/policies.tsx @@ -1,8 +1,8 @@ import { RealtimePolicies } from 'components/interfaces/Realtime/Policies' import type { NextPageWithLayout } from 'types' -import RealtimeLayout from 'components/layouts/RealtimeLayout/RealtimeLayout' import DefaultLayout from 'components/layouts/DefaultLayout' +import RealtimeLayout from 'components/layouts/RealtimeLayout/RealtimeLayout' const RealtimePoliciesPage: NextPageWithLayout = () => { return diff --git a/apps/studio/pages/project/[ref]/settings/addons.tsx b/apps/studio/pages/project/[ref]/settings/addons.tsx index d9bf4b6368479..c24befee04940 100644 --- a/apps/studio/pages/project/[ref]/settings/addons.tsx +++ b/apps/studio/pages/project/[ref]/settings/addons.tsx @@ -1,4 +1,4 @@ -import Addons from 'components/interfaces/Settings/Addons/Addons' +import { Addons } from 'components/interfaces/Settings/Addons/Addons' import DefaultLayout from 'components/layouts/DefaultLayout' import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout' import { diff --git a/apps/studio/pages/project/[ref]/settings/auth.tsx b/apps/studio/pages/project/[ref]/settings/auth.tsx deleted file mode 100644 index ce87957dfd9c0..0000000000000 --- a/apps/studio/pages/project/[ref]/settings/auth.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useParams } from 'common' -import DefaultLayout from 'components/layouts/DefaultLayout' -import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout' -import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' -import { ChevronRight } from 'lucide-react' -import Link from 'next/link' -import type { NextPageWithLayout } from 'types' - -const ProjectSettings: NextPageWithLayout = () => { - const { ref } = useParams() as { ref: string } - - return ( - -
-

- All settings are now under configuration within the Authentication page. -

- - General user signup - - - - Password settings in email provider - - - - User sessions - - - - Refresh tokens - - - - Bot and abuse protection - - - - SMTP settings - - - - Access token expiry - - - - Multifactor authentication - - - - Third party authentication - - - - Max request duration - - - - Max direct database connections - - -
-
- ) -} - -ProjectSettings.getLayout = (page) => ( - - {page} - -) - -export default ProjectSettings diff --git a/apps/studio/pages/project/[ref]/settings/general.tsx b/apps/studio/pages/project/[ref]/settings/general.tsx index 894cf9b36eb72..11943bbd52f38 100644 --- a/apps/studio/pages/project/[ref]/settings/general.tsx +++ b/apps/studio/pages/project/[ref]/settings/general.tsx @@ -20,7 +20,8 @@ const ProjectSettings: NextPageWithLayout = () => { const { data: selectedOrganization } = useSelectedOrganizationQuery() const isBranch = !!project?.parent_project_ref - const { projectsTransfer: projectTransferEnabled } = useIsFeatureEnabled(['projects:transfer']) + const { projectsTransfer: projectTransferEnabled, projectSettingsCustomDomains } = + useIsFeatureEnabled(['projects:transfer', 'project_settings:custom_domains']) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: selectedOrganization?.slug }) const hasHipaaAddon = subscriptionHasHipaaAddon(subscription) @@ -37,7 +38,7 @@ const ProjectSettings: NextPageWithLayout = () => { {/* this is only settable on compliance orgs, currently that means HIPAA orgs */} {!isBranch && hasHipaaAddon && } - + {projectSettingsCustomDomains && } {!isBranch && projectTransferEnabled && } {!isBranch && } diff --git a/apps/studio/pages/project/[ref]/settings/jwt/index.tsx b/apps/studio/pages/project/[ref]/settings/jwt/index.tsx index 07f3565d8572c..02364d45227ed 100644 --- a/apps/studio/pages/project/[ref]/settings/jwt/index.tsx +++ b/apps/studio/pages/project/[ref]/settings/jwt/index.tsx @@ -8,16 +8,22 @@ import { JwtSecretUpdateError, JwtSecretUpdateStatus } from '@supabase/shared-ty import { useQueryClient } from '@tanstack/react-query' import { useParams } from 'common' import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from 'components/interfaces/JwtSecrets/jwt.constants' +import { UnknownInterface } from 'components/ui/UnknownInterface' import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' import { configKeys } from 'data/config/keys' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useEffect, useRef } from 'react' import { toast } from 'sonner' const JWTKeysLegacyPage: NextPageWithLayout = () => { - const { ref: projectRef, source } = useParams() const client = useQueryClient() + const { ref: projectRef } = useParams() + const { projectSettingsLegacyJwtKeys } = useIsFeatureEnabled(['project_settings:legacy_jwt_keys']) - const { data } = useJwtSecretUpdatingStatusQuery({ projectRef }) + const { data } = useJwtSecretUpdatingStatusQuery( + { projectRef }, + { enabled: projectSettingsLegacyJwtKeys } + ) const jwtSecretUpdateStatus = data?.jwtSecretUpdateStatus const jwtSecretUpdateError = data?.jwtSecretUpdateError @@ -44,14 +50,20 @@ const JWTKeysLegacyPage: NextPageWithLayout = () => { previousJwtSecretUpdateStatus.current = jwtSecretUpdateStatus }, [jwtSecretUpdateStatus]) - return + if (!projectSettingsLegacyJwtKeys) { + return + } + + return ( + + + + ) } JWTKeysLegacyPage.getLayout = (page) => ( - - {page} - + {page} ) diff --git a/apps/www/_blog/2025-08-20-lw15-hackathon-winners.mdx b/apps/www/_blog/2025-08-20-lw15-hackathon-winners.mdx new file mode 100644 index 0000000000000..acf9ab4d1103f --- /dev/null +++ b/apps/www/_blog/2025-08-20-lw15-hackathon-winners.mdx @@ -0,0 +1,123 @@ +--- +title: 'Supabase Launch Week 15 Hackathon Winner Announcement' +description: Announcing the winners of the Supabase Launch Week 15 Hackathon. +author: tyler_shukert +image: launch-week-15/hackathon-winners/lw15-hackathon.png +thumb: launch-week-15/hackathon-winners/lw15-hackathon.png +launchweek: '15' +categories: + - launch-week +tags: + - launch-week + - hackathon +date: '2025-08-20:08:00' +toc_depth: 2 +--- + +[Launch Week 15](https://supabase.com/launch-week) brought an incredible array of new Supabase features that got developers' creative engines revving. To harness this excitement, we challenged our amazing community with the Launch Week 15 Hackathon - and wow, did they deliver! + +The submissions were truly impressive, showcasing exceptional technical skill and creativity. Our team thoroughly enjoyed reviewing each project, making it challenging to select winners from such a strong pool of entries. + +## Best overall project + +### Winner + +[Figma AI Tickets](https://github.com/vickywane/figma-ai-tickets) by [vickywane](https://github.com/vickywane) + +The supabase-powered Figma plugin. It transforms design work into actionable development tasks automatically. The plugin exports selected frames from Figma as PNGs, feeds them into OpenAI’s GPT-4 model, and intelligently generates detailed user stories. + +![Figma AI Tickets](/images/blog/launch-week-15/hackathon-winners/figma-ai-tickets.png) + +### Runner Up + +[Cozy Keys](https://github.com/sultson/cozy-keys) by [sultson](https://github.com/sultson) + +Play, learn and record piano keys right from the browser. + +![Cozy Keys](/images/blog/launch-week-15/hackathon-winners/cozy-keys.png) + +## Best use of AI + +### Winner + +[Aryn - Spatial Intelligence for Curated Knowledge ](https://github.com/adlai88/airena) by [adlai88](https://github.com/adlai88) + +Aryn transforms Are.na collections into intelligent spatial canvases using AI inferences, pgvector with Edge Functions for clustering analysis. + +![Aryn - Spatial Intelligence for Curated Knowledge ](/images/blog/launch-week-15/hackathon-winners/aryn.png) + +### Runner Up + +[SommIA](https://github.com/SommIAapp/mvp) by [SommIAapp](https://github.com/SommIAapp) + +Your personal AI sommelier, take a photo or describe what you're going to eat and the AI gives you the best wine in your budget to go with it, or at the restaurant take a photo of the wine list and write down what you're ordering to find out the best wine to go with it. + +![SommIA](/images/blog/launch-week-15/hackathon-winners/sommia.png) + +## Most fun / best easter egg + +### Winner + +[SupaClicker](https://github.com/Steellgold/supa-clicker) by [Steellgold](https://github.com/Steellgold) + +An addictive incremental clicker game, click the SUPA, earn power, buy upgrades, and compete with players worldwide in this highly polished clicker game that goes far beyond simple clicking! + +![SupaClicker](/images/blog/launch-week-15/hackathon-winners/supaclicker.png) + +### Runner Up + +[LLM Breakout](https://github.com/akshaykripalani/supahack-lw15) by [akshaykripalani](https://github.com/akshaykripalani) and [MananGandhi1810](https://github.com/MananGandhi1810) + +LLM Breakout is a browser-based twist on the classic brick-breaker game. A tastefully sloppy homage to breakout - where your bricks are generated by prompts, rather than being pre-set colorful bricks. + +![LLM Breakout](/images/blog/launch-week-15/hackathon-winners/llm-breakout.png) + +## Most technically impressive + +### Winner + +[Thrink](https://github.com/benjamin-anenu/-Thrink-) by [benjamin-anenu](https://github.com/benjamin-anenu) + +Thrink is an AI-powered project management platform built on Supabase, covering the full project lifecycle. It features intelligent assistance via OpenRouter.ai (Tink Chat), a sleek frontend developed with Lovable.dev, documentation powered by Claude AI, and AI-driven code reviews using Cursor. + +![Thrink](/images/blog/launch-week-15/hackathon-winners/thrink.png) + +### Runner Up + +[Save Now Earn Later](https://github.com/rrh1441/savenow) by [rrh1441](https://github.com/rrh1441) + +This app shows users how much wealth they could build by investing their small daily purchases (like coffee or lunch) in the S&P 500 instead of spending. + +![Save Now Earn Later](/images/blog/launch-week-15/hackathon-winners/save.png) + +## Most visually pleasing + +### Winner + +[SupaLearn](https://github.com/imprakashraghu/supalearn) by [imprakashraghu](https://github.com/imprakashraghu) + +Learning Science using a game, gain knowledge about periodic elements and chemical reactions by having fun using OpenAI and Supabase. Know about reactions, molecules and compounds while you simulate your own laboratory and playground area to experiment various compounds of your choice. + +![SupaLearn](/images/blog/launch-week-15/hackathon-winners/supalearn.png) + +### Runner Up + +[NTB STICKER SUPAFINDING](https://github.com/Fur1uss/NTB-STICKERS-SUPAFINDING) by [Fur1uss](https://github.com/Fur1uss), [cisarsg](https://github.com/cisarsg), [CazuelaDePollo](https://github.com/CazuelaDePollo), and [Isidora-S-Lee](https://github.com/Isidora-S-Lee) + +NTB STICKER SUPAFINDING is a fast-paced web mini-game where players must find hidden stickers scattered across the screen before time runs out. Each correct sticker adds bonus time, encouraging quick thinking and sharp eyes. Designed with a playful interface, it's a fun challenge that tests your observation skills under pressure. With AI! + +![NTB STICKER SUPAFINDING](/images/blog/launch-week-15/hackathon-winners/ntb.png) + +## The Prizes + +![Keyboard](/images/blog/launch-week-15/hackathon-winners/keyboard.png) + +The winner of the best overall project will receive a keyboard, and each winner and runner-up will receive a Supabase swag kit. + +## Getting Started Guides + +If you're inspired to build, check out some of the latest resources: + +- [Quick Start Guides](https://supabase.com/docs/guides/getting-started) +- [AI & Vectors Guide](https://supabase.com/docs/guides/ai) +- [Edge Functions Guide](https://supabase.com/docs/guides/functions) diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/aryn.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/aryn.png new file mode 100644 index 0000000000000..59740dbed7539 Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/aryn.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/cozy-keys.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/cozy-keys.png new file mode 100644 index 0000000000000..5ac2bbb4c6ed1 Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/cozy-keys.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/figma-ai-tickets.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/figma-ai-tickets.png new file mode 100644 index 0000000000000..701d98bbcf356 Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/figma-ai-tickets.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/keyboard.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/keyboard.png new file mode 100644 index 0000000000000..f26b475a95b0b Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/keyboard.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/llm-breakout.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/llm-breakout.png new file mode 100644 index 0000000000000..16691a1b6952f Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/llm-breakout.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/lw15-hackathon.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/lw15-hackathon.png new file mode 100644 index 0000000000000..ed39b4c63832f Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/lw15-hackathon.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/ntb.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/ntb.png new file mode 100644 index 0000000000000..06eba031d1f19 Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/ntb.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/save.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/save.png new file mode 100644 index 0000000000000..d5f4a48738b62 Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/save.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/sommia.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/sommia.png new file mode 100644 index 0000000000000..7f36d38506436 Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/sommia.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/supaclicker.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/supaclicker.png new file mode 100644 index 0000000000000..a1dfc6ebb8ca2 Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/supaclicker.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/supalearn.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/supalearn.png new file mode 100644 index 0000000000000..d9995e48cab21 Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/supalearn.png differ diff --git a/apps/www/public/images/blog/launch-week-15/hackathon-winners/thrink.png b/apps/www/public/images/blog/launch-week-15/hackathon-winners/thrink.png new file mode 100644 index 0000000000000..09885359e78a4 Binary files /dev/null and b/apps/www/public/images/blog/launch-week-15/hackathon-winners/thrink.png differ diff --git a/packages/common/enabled-features/enabled-features.json b/packages/common/enabled-features/enabled-features.json index f2d63d6684692..41de346f04d7f 100644 --- a/packages/common/enabled-features/enabled-features.json +++ b/packages/common/enabled-features/enabled-features.json @@ -6,6 +6,16 @@ "ai:opt_in_level_schema_and_log": true, "ai:opt_in_level_schema_and_log_and_data": true, + "authentication:sign_in_providers": true, + "authentication:third_party_auth": true, + "authentication:rate_limits": true, + "authentication:emails": true, + "authentication:multi_factor": true, + "authentication:attack_protection": true, + "authentication:advanced": true, + + "authentication:show_providers": true, + "authentication:show_manual_linking": true, "authentication:show_send_invitation": true, "authentication:show_provider_filter": true, "authentication:show_sort_by_email": true, @@ -18,12 +28,15 @@ "database:roles": true, "integrations:vercel": true, + "integrations:show_stripe_wrapper": true, "logs:templates": true, "logs:collections": true, "profile:show_email": true, "profile:show_information": true, + "profile:show_analytics_and_marketing": true, + "profile:show_account_deletion": true, "project_connection:javascript_example": true, "project_connection:dart_example": true, @@ -34,5 +47,11 @@ "project_homepage:show_instance_size": true, "project_homepage:show_examples": true, - "table_editor:enable_rls_toggle": true + "project_addons:dedicated_ipv4_address": true, + "project_settings:custom_domains": true, + "project_settings:show_disable_legacy_api_keys": true, + "project_settings:legacy_jwt_keys": true, + "project_settings:log_drains": true, + + "reports:all": true } diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json index 22081cf423dff..f75bab7b4b590 100644 --- a/packages/common/enabled-features/enabled-features.schema.json +++ b/packages/common/enabled-features/enabled-features.schema.json @@ -23,6 +23,43 @@ "description": "Enable the AI opt in level 'schema_and_log_and_data'" }, + "authentication:sign_in_providers": { + "type": "boolean", + "description": "Enable the authentication sign in / providers page" + }, + "authentication:third_party_auth": { + "type": "boolean", + "description": "Enable the authentication third party auth page" + }, + "authentication:rate_limits": { + "type": "boolean", + "description": "Enable the authentication rate limits page" + }, + "authentication:emails": { + "type": "boolean", + "description": "Enable the authentication emails page" + }, + "authentication:multi_factor": { + "type": "boolean", + "description": "Enable the authentication multi factor page" + }, + "authentication:attack_protection": { + "type": "boolean", + "description": "Enable the authentication attack protection page" + }, + "authentication:advanced": { + "type": "boolean", + "description": "Enable the authentication advanced page" + }, + + "authentication:show_providers": { + "type": "boolean", + "description": "Show the providers section in the authentication Sign In / Providers page" + }, + "authentication:show_manual_linking": { + "type": "boolean", + "description": "Show the manual linking toggle in the authentication Sign In / Providers page under User Signups" + }, "authentication:show_send_invitation": { "type": "boolean", "description": "Show the send invitation option in the authentication users page" @@ -62,6 +99,10 @@ "type": "boolean", "description": "Enable the vercel integration section in the organization and project settings pages" }, + "integrations:show_stripe_wrapper": { + "type": "boolean", + "description": "Show the Stripe wrapper under project integrations" + }, "logs:templates": { "type": "boolean", @@ -80,6 +121,14 @@ "type": "boolean", "description": "Shows the user's profile information (first name, last name) in account preferences" }, + "profile:show_analytics_and_marketing": { + "type": "boolean", + "description": "Shows the analytics and marketing section in account preferences" + }, + "profile:show_account_deletion": { + "type": "boolean", + "description": "Shows the account deletion section in account preferences" + }, "project_connection:javascript_example": { "type": "boolean", @@ -108,9 +157,31 @@ "description": "Show all client libraries examples in the project homepage. When false, all client library examples will be hidden except the JavaScript client library." }, - "table_editor:enable_rls_toggle": { + "project_addons:dedicated_ipv4_address": { + "type": "boolean", + "description": "Show the dedicated IPv4 address addon" + }, + + "project_settings:custom_domains": { + "type": "boolean", + "description": "Show the custom domain configuration section + custom domain addon" + }, + "project_settings:show_disable_legacy_api_keys": { + "type": "boolean", + "description": "Show the disable legacy API keys section in project settings API Keys page" + }, + "project_settings:legacy_jwt_keys": { + "type": "boolean", + "description": "Enable the legacy JWT secret page in project settings" + }, + "project_settings:log_drains": { + "type": "boolean", + "description": "Enable the log drains page in project settings" + }, + + "reports:all": { "type": "boolean", - "description": "Show the RLS toggle in the table creation flow and table editor header" + "description": "Enable the project reports page" } }, "required": [ @@ -118,6 +189,15 @@ "ai:opt_in_level_schema", "ai:opt_in_level_schema_and_log", "ai:opt_in_level_schema_and_log_and_data", + "authentication:sign_in_providers", + "authentication:third_party_auth", + "authentication:rate_limits", + "authentication:emails", + "authentication:multi_factor", + "authentication:attack_protection", + "authentication:advanced", + "authentication:show_providers", + "authentication:show_manual_linking", "authentication:show_provider_filter", "authentication:show_send_invitation", "authentication:show_sort_by_email", @@ -127,17 +207,25 @@ "database:replication", "database:roles", "integrations:vercel", + "integrations:show_stripe_wrapper", "profile:show_email", "profile:show_information", + "profile:show_analytics_and_marketing", + "profile:show_account_deletion", "logs:templates", "logs:collections", "project_creation:show_advanced_config", "project_homepage:show_instance_size", "project_homepage:show_examples", "project_homepage:show_all_client_libraries", + "project_addons:dedicated_ipv4_address", + "project_settings:custom_domains", + "project_settings:show_disable_legacy_api_keys", + "project_settings:legacy_jwt_keys", + "project_settings:log_drains", "project_connection:javascript_example", "project_connection:dart_example", - "table_editor:enable_rls_toggle" + "reports:all" ], "additionalProperties": false } diff --git a/packages/ui/src/components/shadcn/ui/text-area.tsx b/packages/ui/src/components/shadcn/ui/text-area.tsx index 3966b409a4ea0..3c24ef458c00f 100644 --- a/packages/ui/src/components/shadcn/ui/text-area.tsx +++ b/packages/ui/src/components/shadcn/ui/text-area.tsx @@ -11,7 +11,7 @@ const TextArea = React.forwardRef( return (