diff --git a/apps/studio/components/grid/components/header/Header.tsx b/apps/studio/components/grid/components/header/Header.tsx index 0985c79f2e8b4..2b2429cd33332 100644 --- a/apps/studio/components/grid/components/header/Header.tsx +++ b/apps/studio/components/grid/components/header/Header.tsx @@ -15,7 +15,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useTableRowsCountQuery } from 'data/table-rows/table-rows-count-query' import { fetchAllTableRows, useTableRowsQuery } from 'data/table-rows/table-rows-query' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { RoleImpersonationState } from 'lib/role-impersonation' @@ -84,8 +84,10 @@ const DefaultHeader = () => { const snap = useTableEditorTableStateSnapshot() const tableEditorSnap = useTableEditorStateSnapshot() - const canCreateColumns = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns') - + const { can: canCreateColumns } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'columns' + ) const { mutate: sendEvent } = useSendEventMutation() const onAddRow = diff --git a/apps/studio/components/interfaces/APIKeys/APIKeyDeleteDialog.tsx b/apps/studio/components/interfaces/APIKeys/APIKeyDeleteDialog.tsx index 90ed55186bab4..f646d50246837 100644 --- a/apps/studio/components/interfaces/APIKeys/APIKeyDeleteDialog.tsx +++ b/apps/studio/components/interfaces/APIKeys/APIKeyDeleteDialog.tsx @@ -1,13 +1,13 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { Trash2 } from 'lucide-react' import { useState } from 'react' import { toast } from 'sonner' -import { Trash2 } from 'lucide-react' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common/hooks' +import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import { useAPIKeyDeleteMutation } from 'data/api-keys/api-key-delete-mutation' import { APIKeysData } from 'data/api-keys/api-keys-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { DropdownMenuItem } from 'ui' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal' interface APIKeyDeleteDialogProps { @@ -19,7 +19,10 @@ export const APIKeyDeleteDialog = ({ apiKey, lastSeen }: APIKeyDeleteDialogProps const { ref: projectRef } = useParams() const [isOpen, setIsOpen] = useState(false) - const canDeleteAPIKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, '*') + const { can: canDeleteAPIKeys } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + '*' + ) const { mutate: deleteAPIKey, isLoading: isDeletingAPIKey } = useAPIKeyDeleteMutation({ onSuccess: () => { @@ -35,17 +38,26 @@ export const APIKeyDeleteDialog = ({ apiKey, lastSeen }: APIKeyDeleteDialogProps return ( <> - { if (canDeleteAPIKeys) { e.preventDefault() setIsOpen(true) } }} + disabled={!canDeleteAPIKeys} + tooltip={{ + content: { + side: 'left', + text: !canDeleteAPIKeys + ? 'You need additional permissions to delete API keys' + : undefined, + }, + }} > Delete API key - + setIsOpen(false)} diff --git a/apps/studio/components/interfaces/APIKeys/ApiKeysIllustrations.tsx b/apps/studio/components/interfaces/APIKeys/ApiKeysIllustrations.tsx index c60ecf73031d4..ff5bbd74c8787 100644 --- a/apps/studio/components/interfaces/APIKeys/ApiKeysIllustrations.tsx +++ b/apps/studio/components/interfaces/APIKeys/ApiKeysIllustrations.tsx @@ -160,12 +160,10 @@ export const ApiKeysCreateCallout = () => { * Feedback banner for users who have API keys and the feature is rolled out to them */ export const ApiKeysFeedbackBanner = () => { - const { hasApiKeys, isInRollout } = useApiKeysVisibility() + const { hasApiKeys } = useApiKeysVisibility() // Don't show anything if not in rollout or if keys don't exist - if (!isInRollout || !hasApiKeys) { - return null - } + if (!hasApiKeys) return null return ( { [apiKeysData] ) - const isPermissionsLoading = !usePermissionsLoaded() - const canReadAPIKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, '*') + const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + '*' + ) // The default publisahble key will always be the first one const apiKey = publishableApiKeys[0] @@ -46,12 +48,12 @@ export const PublishableAPIKeys = () => {
@@ -65,31 +67,29 @@ export const PublishableAPIKeys = () => { )} -
- {/* */} - {/* @mildtomato - To add in later with follow up PR */} - {/* */} -
- - {/* */} ) } -function ApiKeyInput() { +const ApiKeyInput = () => { const { ref: projectRef } = useParams() + const { data: apiKeysData, isLoading: isApiKeysLoading, error, } = useAPIKeysQuery({ projectRef, reveal: false }) + const publishableApiKeys = useMemo( () => apiKeysData?.filter(({ type }) => type === 'publishable') ?? [], [apiKeysData] ) - const isPermissionsLoading = !usePermissionsLoaded() - const canReadAPIKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, '*') + + const { can: canReadAPIKeys, isLoading: isPermissionsLoading } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + '*' + ) // The default publisahble key will always be the first one const apiKey = publishableApiKeys[0] diff --git a/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx b/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx index 5f6c6e1b83f64..273b0cb6e640f 100644 --- a/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx +++ b/apps/studio/components/interfaces/APIKeys/SecretAPIKeys.tsx @@ -1,15 +1,12 @@ -import dayjs from 'dayjs' -import duration from 'dayjs/plugin/duration' -import relativeTime from 'dayjs/plugin/relativeTime' - import { PermissionAction } from '@supabase/shared-types/out/constants' +import dayjs from 'dayjs' import { useMemo, useRef } from 'react' import { useParams } from 'common' import { FormHeader } from 'components/ui/Forms/FormHeader' import { APIKeysData, useAPIKeysQuery } from 'data/api-keys/api-keys-query' -import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' import useLogsQuery from 'hooks/analytics/useLogsQuery' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { Card, CardContent, EyeOffIcon, Skeleton, WarningIcon, cn } from 'ui' import { Table, @@ -22,9 +19,6 @@ import { import { APIKeyRow } from './APIKeyRow' import CreateSecretAPIKeyDialog from './CreateSecretAPIKeyDialog' -dayjs.extend(duration) -dayjs.extend(relativeTime) - interface LastSeenData { [hash: string]: { timestamp: string } } @@ -62,8 +56,10 @@ export const SecretAPIKeys = () => { error, } = useAPIKeysQuery({ projectRef, reveal: false }) - const isLoadingPermissions = !usePermissionsLoaded() - const canReadAPIKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, '*') + const { can: canReadAPIKeys, isLoading: isLoadingPermissions } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + '*' + ) const lastSeen = useLastSeen(projectRef!) diff --git a/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts b/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts index caf6c85b010e3..f2b602522ef06 100644 --- a/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts +++ b/apps/studio/components/interfaces/APIKeys/hooks/useApiKeysVisibility.ts @@ -1,13 +1,11 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' import { useMemo } from 'react' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { useAPIKeysQuery } from 'data/api-keys/api-keys-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { useFlag } from 'hooks/ui/useFlag' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' interface ApiKeysVisibilityState { - isInRollout: boolean hasApiKeys: boolean isLoading: boolean canReadAPIKeys: boolean @@ -21,8 +19,7 @@ interface ApiKeysVisibilityState { */ export function useApiKeysVisibility(): ApiKeysVisibilityState { const { ref: projectRef } = useParams() - const canReadAPIKeys = useCheckPermissions(PermissionAction.READ, 'api_keys') - const isBasicApiKeysEnabled = useFlag('basicApiKeys') + const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions(PermissionAction.READ, 'api_keys') const { data: apiKeysData, isLoading } = useAPIKeysQuery({ projectRef, @@ -39,13 +36,12 @@ export function useApiKeysVisibility(): ApiKeysVisibilityState { const hasApiKeys = publishableApiKeys.length > 0 // Can initialize API keys when in rollout, has permissions, not loading, and no API keys yet - const canInitApiKeys = isBasicApiKeysEnabled && canReadAPIKeys && !isLoading && !hasApiKeys + const canInitApiKeys = canReadAPIKeys && !isLoading && !hasApiKeys // Disable UI for publishable keys and secrets keys if flag is not enabled OR no API keys created yet - const shouldDisableUI = !isBasicApiKeysEnabled || !hasApiKeys + const shouldDisableUI = !hasApiKeys return { - isInRollout: isBasicApiKeysEnabled, hasApiKeys, isLoading, canReadAPIKeys, diff --git a/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx index 65357542272ad..9f498c43c16fc 100644 --- a/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx @@ -12,7 +12,7 @@ import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' import { @@ -43,18 +43,19 @@ const FormSchema = z.object({ export const AdvancedAuthSettingsForm = () => { const { ref: projectRef } = useParams() const { data: organization } = useSelectedOrganizationQuery() - const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canReadConfig } = useAsyncCheckProjectPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) const [isUpdatingRequestDurationForm, setIsUpdatingRequestDurationForm] = useState(false) const [isUpdatingDatabaseForm, setIsUpdatingDatabaseForm] = useState(false) - const { - data: authConfig, - error: authConfigError, - isLoading, - isError, - } = useAuthConfigQuery({ projectRef }) + const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx index a5b9834684cdc..1b118c25af6fb 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx @@ -24,10 +24,10 @@ import { ProviderForm } from './ProviderForm' export const AuthProvidersForm = () => { const { ref: projectRef } = useParams() const { - isLoading, + data: authConfig, error: authConfigError, + isLoading, isError, - data: authConfig, isSuccess, } = useAuthConfigQuery({ projectRef }) diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx b/apps/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx index c1fcb13167482..80284a6dec9f4 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx @@ -1,5 +1,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { Check } from 'lucide-react' +import { useQueryState } from 'nuqs' import { useEffect, useState } from 'react' import ReactMarkdown from 'react-markdown' import { toast } from 'sonner' @@ -13,9 +14,8 @@ import type { components } from 'data/api' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { BASE_PATH } from 'lib/constants' -import { useQueryState } from 'nuqs' import { Button, Form, Input, Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle } from 'ui' import { Admonition } from 'ui-patterns' import { NO_REQUIRED_CHARACTERS } from '../Auth.constants' @@ -23,12 +23,14 @@ import { AuthAlert } from './AuthAlert' import type { Provider } from './AuthProvidersForm.types' import FormField from './FormField' -export interface ProviderFormProps { +interface ProviderFormProps { config: components['schemas']['GoTrueConfigResponse'] provider: Provider isActive: boolean } +const doubleNegativeKeys = ['SMS_AUTOCONFIRM'] + export const ProviderForm = ({ config, provider, isActive }: ProviderFormProps) => { const { ref: projectRef } = useParams() const [urlProvider, setUrlProvider] = useQueryState('provider', { defaultValue: '' }) @@ -36,8 +38,7 @@ export const ProviderForm = ({ config, provider, isActive }: ProviderFormProps) const [open, setOpen] = useState(false) const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation() - const doubleNegativeKeys = ['SMS_AUTOCONFIRM'] - const canUpdateConfig: boolean = useCheckPermissions( + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) diff --git a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx index 8961d29c60a62..4409bc24e5401 100644 --- a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx @@ -13,7 +13,7 @@ import { InlineLink } from 'components/ui/InlineLink' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -29,6 +29,7 @@ import { WarningIcon, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' import { NO_REQUIRED_CHARACTERS } from '../Auth.constants' const schema = object({ @@ -41,11 +42,23 @@ const schema = object({ const BasicAuthSettingsForm = () => { const { ref: projectRef } = useParams() - const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) + const { + data: authConfig, + error: authConfigError, + isError, + isSuccess, + isLoading, + } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation() - const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canReadConfig, isSuccess: isPermissionsLoaded } = useAsyncCheckProjectPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) const form = useForm({ resolver: yupResolver(schema), @@ -95,207 +108,223 @@ const BasicAuthSettingsForm = () => { ) } - if (isError) { - return ( - - - Failed to retrieve auth configuration - {authConfigError.message} - - ) - } - - if (!canReadConfig) { - return ( -
- -
- ) - } - return ( User Signups - -
- - - ( - - - - - - )} - /> - - - ( - - Enable{' '} - - manual linking APIs - {' '} - for your project - - } - > - - - - - )} - /> - - - ( - - Enable{' '} - - anonymous sign-ins - {' '} - for your project - - } - > - - - - - )} - /> + {isError && ( + + + Failed to retrieve auth configuration + {authConfigError.message} + + )} - {form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && ( - - -
- - Anonymous users will use the authenticated{' '} - role when signing in - - -

- As a result, anonymous users will be subjected to RLS policies that apply to - the public and{' '} - authenticated roles. We strongly advise{' '} - - reviewing your RLS policies - {' '} - to ensure that access to your data is restricted where required. -

- -
-
-
- )} + {isPermissionsLoaded && !canReadConfig && ( +
+ +
+ )} + + {isLoading && ( + + + + + + + + + + + + + + + + )} - {!authConfig?.SECURITY_CAPTCHA_ENABLED && - form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && ( - + {isSuccess && ( + + + + + ( + + + + + + )} + /> + + + ( + + Enable{' '} + + manual linking APIs + {' '} + for your project + + } + > + + + + + )} + /> + + + ( + + Enable{' '} + + anonymous sign-ins + {' '} + for your project + + } + > + + + + + )} + /> + + {form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && ( + - - We highly recommend{' '} - - enabling captcha - {' '} - for anonymous sign-ins - - - This will prevent potential abuse on sign-ins which may bloat your database - and incur costs for monthly active users (MAU) - +
+ + Anonymous users will use the authenticated{' '} + role when signing in + + +

+ As a result, anonymous users will be subjected to RLS policies that apply + to the public and{' '} + authenticated roles. We strongly advise{' '} + + reviewing your RLS policies + {' '} + to ensure that access to your data is restricted where required. +

+ +
+
)} -
- - ( - - - - - + + {!authConfig?.SECURITY_CAPTCHA_ENABLED && + form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && ( + + + + We highly recommend{' '} + + enabling captcha + {' '} + for anonymous sign-ins + + + This will prevent potential abuse on sign-ins which may bloat your database + and incur costs for monthly active users (MAU) + + + )} + + + ( + + + + + + )} + /> + + + {form.formState.isDirty && ( + )} - /> -
- - {form.formState.isDirty && ( - - )} - - -
-
-
+ + + + + )}
) } diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx index e1448112fb564..ae576925a302a 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/TemplateEditor.tsx @@ -2,21 +2,19 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { Code, Monitor } from 'lucide-react' import { editor } from 'monaco-editor' import { useEffect, useMemo, useRef, useState } from 'react' +import { useForm } from 'react-hook-form' import ReactMarkdown from 'react-markdown' import { toast } from 'sonner' -import { useForm } from 'react-hook-form' import { useParams } from 'common' import CodeEditor from 'components/ui/CodeEditor/CodeEditor' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useValidateSpamMutation, ValidateSpamResponse } from 'data/auth/validate-spam-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import type { FormSchema } from 'types' import { - Badge, Button, - Card, CardContent, CardFooter, Form_Shadcn_, @@ -42,7 +40,10 @@ interface TemplateEditorProps { const TemplateEditor = ({ template }: TemplateEditorProps) => { const { ref: projectRef } = useParams() - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) // Add a ref to the code editor const editorRef = useRef() diff --git a/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx b/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx index 87ca544e5c4e1..0cb2986486950 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/AddHookDropdown.tsx @@ -4,7 +4,7 @@ import { ChevronDown } from 'lucide-react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useAuthConfigQuery } from 'data/auth/auth-config-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { Button, @@ -33,7 +33,10 @@ export const AddHookDropdown = ({ const { data: organization } = useSelectedOrganizationQuery() const { data: authConfig } = useAuthConfigQuery({ projectRef }) - const canUpdateAuthHook = useCheckPermissions(PermissionAction.AUTH_EXECUTE, '*') + const { can: canUpdateAuthHook } = useAsyncCheckProjectPermissions( + PermissionAction.AUTH_EXECUTE, + '*' + ) const hooks: Hook[] = HOOKS_DEFINITIONS.map((definition) => { return { diff --git a/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx b/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx index d626ef3efee4d..cf5b65112e04a 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx @@ -1,11 +1,11 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { Check, Webhook } from 'lucide-react' -import { Badge, Input, copyToClipboard } from 'ui' import { ButtonTooltip } from 'components/ui/ButtonTooltip' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { Hook } from './hooks.constants' import { DocsButton } from 'components/ui/DocsButton' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { Badge, Input, copyToClipboard } from 'ui' +import { Hook } from './hooks.constants' interface HookCardProps { hook: Hook @@ -13,7 +13,10 @@ interface HookCardProps { } export const HookCard = ({ hook, onSelect }: HookCardProps) => { - const canUpdateAuthHook = useCheckPermissions(PermissionAction.AUTH_EXECUTE, '*') + const { can: canUpdateAuthHook } = useAsyncCheckProjectPermissions( + PermissionAction.AUTH_EXECUTE, + '*' + ) return (
diff --git a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx index 4a00780db53a5..90a1c353c4917 100644 --- a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx @@ -11,7 +11,7 @@ import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' import { @@ -87,8 +87,14 @@ const MfaAuthSettingsForm = () => { const [isUpdatingTotpForm, setIsUpdatingTotpForm] = useState(false) const [isUpdatingPhoneForm, setIsUpdatingPhoneForm] = useState(false) - const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canReadConfig } = useAsyncCheckProjectPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) const { data: organization } = useSelectedOrganizationQuery() const isProPlanAndUp = organization?.plan?.id !== 'free' diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx index 259f5e7fd0198..57a9340330396 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/PolicyDetailsV2.tsx @@ -1,11 +1,11 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' import { Check, ChevronsUpDown } from 'lucide-react' import { useEffect, useState } from 'react' import { UseFormReturn } from 'react-hook-form' -import { PermissionAction } from '@supabase/shared-types/out/constants' import { useDatabaseRolesQuery } from 'data/database-roles/database-roles-query' import { useTablesQuery } from 'data/tables/tables-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, @@ -62,7 +62,10 @@ export const PolicyDetailsV2 = ({ }: PolicyDetailsV2Props) => { const { data: project } = useSelectedProjectQuery() const [open, setOpen] = useState(false) - const canUpdatePolicies = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') + const { can: canUpdatePolicies } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'tables' + ) const { data: tables, isSuccess: isSuccessTables } = useTablesQuery({ projectRef: project?.ref, diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx index da7f29f264c94..ac3c2e99e3807 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyEditorPanel/index.tsx @@ -15,7 +15,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useDatabasePolicyUpdateMutation } from 'data/database-policies/database-policy-update-mutation' import { databasePoliciesKeys } from 'data/database-policies/keys' import { QueryResponseError, useExecuteSqlMutation } from 'data/sql/execute-sql-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { Button, @@ -67,7 +67,10 @@ export const PolicyEditorPanel = memo(function ({ const queryClient = useQueryClient() const { data: selectedProject } = useSelectedProjectQuery() - const canUpdatePolicies = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') + const { can: canUpdatePolicies } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'tables' + ) // [Joshen] Hyrid form fields, just spit balling to get a decent POC out const [using, setUsing] = useState('') diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx index 2bb0216f8004e..c58cc46fa172b 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx @@ -6,7 +6,7 @@ import { Edit, MoreVertical, Trash } from 'lucide-react' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import Panel from 'components/ui/Panel' import { useAuthConfigQuery } from 'data/auth/auth-config-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { @@ -38,7 +38,10 @@ const PolicyRow = ({ onSelectDeletePolicy = noop, }: PolicyRowProps) => { const aiSnap = useAiAssistantStateSnapshot() - const canUpdatePolicies = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'policies') + const { can: canUpdatePolicies } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'policies' + ) const { data: project } = useSelectedProjectQuery() const { data: authConfig } = useAuthConfigQuery({ projectRef: project?.ref }) diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx index c668c505a3d33..6858191349ad2 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx @@ -5,7 +5,7 @@ import { Lock, Unlock } from 'lucide-react' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state' import { AiIconAnimation, Badge } from 'ui' @@ -35,8 +35,14 @@ const PolicyTableRowHeader = ({ const { ref } = useParams() const aiSnap = useAiAssistantStateSnapshot() - const canCreatePolicies = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'policies') - const canToggleRLS = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') + const { can: canCreatePolicies } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'policies' + ) + const { can: canToggleRLS } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'tables' + ) const isRealtimeSchema = table.schema === 'realtime' const isRealtimeMessagesTable = isRealtimeSchema && table.name === 'messages' diff --git a/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx index 98370bad5384f..8f9dae46e2937 100644 --- a/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx @@ -12,7 +12,7 @@ import { InlineLink } from 'components/ui/InlineLink' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -69,18 +69,25 @@ const schema = object({ const ProtectionAuthSettingsForm = () => { const { ref: projectRef } = useParams() - const { - data: authConfig, - error: authConfigError, - isLoading, - isError, - } = useAuthConfigQuery({ projectRef }) - const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation() - const [isUpdatingProtection, setIsUpdatingProtection] = useState(false) + const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) + const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation({ + onError: (error) => { + toast.error(`Failed to update settings: ${error?.message}`) + }, + onSuccess: () => { + toast.success('Successfully updated settings') + }, + }) const [hidden, setHidden] = useState(true) - const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canReadConfig } = useAsyncCheckProjectPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) const protectionForm = useForm({ resolver: yupResolver(schema), @@ -102,7 +109,7 @@ const ProtectionAuthSettingsForm = () => { }) useEffect(() => { - if (authConfig && !isUpdatingProtection) { + if (authConfig && !isUpdatingConfig) { protectionForm.reset({ DISABLE_SIGNUP: !authConfig.DISABLE_SIGNUP, EXTERNAL_ANONYMOUS_USERS_ENABLED: authConfig.EXTERNAL_ANONYMOUS_USERS_ENABLED || false, @@ -120,11 +127,9 @@ const ProtectionAuthSettingsForm = () => { PASSWORD_HIBP_ENABLED: authConfig.PASSWORD_HIBP_ENABLED || false, }) } - }, [authConfig, isUpdatingProtection]) + }, [authConfig, isUpdatingConfig]) const onSubmitProtection = (values: any) => { - setIsUpdatingProtection(true) - const payload = { ...values } payload.DISABLE_SIGNUP = !values.DISABLE_SIGNUP // The backend uses empty string to represent no required characters in the password @@ -132,19 +137,7 @@ const ProtectionAuthSettingsForm = () => { payload.PASSWORD_REQUIRED_CHARACTERS = '' } - updateAuthConfig( - { projectRef: projectRef!, config: payload }, - { - onError: (error) => { - toast.error(`Failed to update settings: ${error?.message}`) - setIsUpdatingProtection(false) - }, - onSuccess: () => { - toast.success('Successfully updated settings') - setIsUpdatingProtection(false) - }, - } - ) + updateAuthConfig({ projectRef: projectRef!, config: payload }) } if (isError) { @@ -282,10 +275,8 @@ const ProtectionAuthSettingsForm = () => { diff --git a/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx b/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx index 30befae90b5f0..7b8ea69e7c1d3 100644 --- a/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx +++ b/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx @@ -13,7 +13,7 @@ import NoPermission from 'components/ui/NoPermission' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Card, @@ -32,8 +32,14 @@ import { isSmtpEnabled } from '../SmtpForm/SmtpForm.utils' const RateLimits = () => { const { ref: projectRef } = useParams() - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') - const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) + const { can: canReadConfig } = useAsyncCheckProjectPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) const { data: authConfig, diff --git a/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx b/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx index 304cac3af4def..fdcf729a56a9d 100644 --- a/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx +++ b/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx @@ -3,7 +3,7 @@ import { Globe, Trash } from 'lucide-react' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { EmptyListState } from 'components/ui/States' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Checkbox_Shadcn_ } from 'ui' import { ValueContainer } from './ValueContainer' @@ -24,7 +24,10 @@ export const RedirectUrlList = ({ onSelectRemoveURLs, onSelectClearSelection, }: RedirectUrlListProps) => { - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) // [Joshen] One for next time: maybe shift this into a reusable logic since it // seems like we can use this in multiple places for future diff --git a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx index 5f9f9d892d621..dfb4b224731f5 100644 --- a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx @@ -11,7 +11,7 @@ import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' import { @@ -68,8 +68,14 @@ const SessionsAuthSettingsForm = () => { const [isUpdatingRefreshTokens, setIsUpdatingRefreshTokens] = useState(false) const [isUpdatingUserSessions, setIsUpdatingUserSessions] = useState(false) - const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canReadConfig } = useAsyncCheckProjectPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) const { data: organization } = useSelectedOrganizationQuery() const isProPlanAndUp = organization?.plan?.id !== 'free' diff --git a/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx b/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx index e9e785b9c19e9..fd8a8a5b8d705 100644 --- a/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx +++ b/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx @@ -1,16 +1,16 @@ +import { yupResolver } from '@hookform/resolvers/yup' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { AlertCircle } from 'lucide-react' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { object, string } from 'yup' -import { yupResolver } from '@hookform/resolvers/yup' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -24,7 +24,7 @@ import { Form_Shadcn_, Input_Shadcn_, } from 'ui' -import { AlertCircle } from 'lucide-react' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' const schema = object({ SITE_URL: string().required('Must have a Site URL'), @@ -36,7 +36,10 @@ const SiteUrl = () => { const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() const [isUpdatingSiteUrl, setIsUpdatingSiteUrl] = useState(false) - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) const siteUrlForm = useForm({ resolver: yupResolver(schema), diff --git a/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx b/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx index e966cd43b4ec0..4acf7b5cd908c 100644 --- a/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx +++ b/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx @@ -1,18 +1,18 @@ import { yupResolver } from '@hookform/resolvers/yup' import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useParams } from 'common' +import { AlertTriangle, Eye, EyeOff } from 'lucide-react' +import Link from 'next/link' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import * as yup from 'yup' +import { useParams } from 'common' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' -import { AlertTriangle, Eye, EyeOff } from 'lucide-react' -import Link from 'next/link' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, @@ -30,7 +30,7 @@ import { WarningIcon, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' -import { urlRegex } from './../Auth.constants' +import { urlRegex } from '../Auth.constants' import { defaultDisabledSmtpFormValues } from './SmtpForm.constants' import { generateFormValues, isSmtpEnabled } from './SmtpForm.utils' @@ -53,8 +53,14 @@ const SmtpForm = () => { const [enableSmtp, setEnableSmtp] = useState(false) const [hidden, setHidden] = useState(true) - const canReadConfig = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canReadConfig } = useAsyncCheckProjectPermissions( + PermissionAction.READ, + 'custom_config_gotrue' + ) + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) const smtpSchema = yup.object({ SMTP_ADMIN_EMAIL: yup.string().when('ENABLE_SMTP', { diff --git a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/index.tsx b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/index.tsx index fdf8cf905b67e..456bf706e53b7 100644 --- a/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/index.tsx +++ b/apps/studio/components/interfaces/Auth/ThirdPartyAuthForm/index.tsx @@ -17,7 +17,7 @@ import { ThirdPartyAuthIntegration, useThirdPartyAuthIntegrationsQuery, } from 'data/third-party-auth/integrations-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { cn } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { AddIntegrationDropdown } from './AddIntegrationDropdown' @@ -49,7 +49,10 @@ export const ThirdPartyAuthForm = () => { useState() const { mutateAsync: deleteIntegration } = useDeleteThirdPartyAuthIntegrationMutation() - const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') + const { can: canUpdateConfig } = useAsyncCheckProjectPermissions( + PermissionAction.UPDATE, + 'custom_config_gotrue' + ) if (isError) { return ( diff --git a/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx b/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx index d602f9fe963d2..4ef7f008d726e 100644 --- a/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx @@ -2,7 +2,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { ChevronDown, Mail, UserPlus } from 'lucide-react' import { useState } from 'react' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { Button, DropdownMenu, @@ -17,8 +17,14 @@ import CreateUserModal from './CreateUserModal' import InviteUserModal from './InviteUserModal' const AddUserDropdown = () => { - const canInviteUsers = useCheckPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user') - const canCreateUsers = useCheckPermissions(PermissionAction.AUTH_EXECUTE, 'create_user') + const { can: canInviteUsers } = useAsyncCheckProjectPermissions( + PermissionAction.AUTH_EXECUTE, + 'invite_user' + ) + const { can: canCreateUsers } = useAsyncCheckProjectPermissions( + PermissionAction.AUTH_EXECUTE, + 'create_user' + ) const [inviteVisible, setInviteVisible] = useState(false) const [createVisible, setCreateVisible] = useState(false) diff --git a/apps/studio/components/interfaces/Auth/Users/CreateUserModal.tsx b/apps/studio/components/interfaces/Auth/Users/CreateUserModal.tsx index 63c4b904996e1..f2b630c369821 100644 --- a/apps/studio/components/interfaces/Auth/Users/CreateUserModal.tsx +++ b/apps/studio/components/interfaces/Auth/Users/CreateUserModal.tsx @@ -7,7 +7,7 @@ import * as z from 'zod' import { useParams } from 'common' import { useUserCreateMutation } from 'data/auth/user-create-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Checkbox_Shadcn_, @@ -38,7 +38,10 @@ const CreateUserFormSchema = z.object({ const CreateUserModal = ({ visible, setVisible }: CreateUserModalProps) => { const { ref: projectRef } = useParams() - const canCreateUsers = useCheckPermissions(PermissionAction.AUTH_EXECUTE, 'create_user') + const { can: canCreateUsers } = useAsyncCheckProjectPermissions( + PermissionAction.AUTH_EXECUTE, + 'create_user' + ) const { mutate: createUser, isLoading: isCreatingUser } = useUserCreateMutation({ onSuccess(res) { diff --git a/apps/studio/components/interfaces/Auth/Users/InviteUserModal.tsx b/apps/studio/components/interfaces/Auth/Users/InviteUserModal.tsx index 59f7d064d3b17..d25657452053e 100644 --- a/apps/studio/components/interfaces/Auth/Users/InviteUserModal.tsx +++ b/apps/studio/components/interfaces/Auth/Users/InviteUserModal.tsx @@ -4,7 +4,7 @@ import { toast } from 'sonner' import { useParams } from 'common' import { useUserInviteMutation } from 'data/auth/user-invite-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { Button, Form, Input, Modal } from 'ui' export type InviteUserModalProps = { @@ -22,7 +22,10 @@ const InviteUserModal = ({ visible, setVisible }: InviteUserModalProps) => { setVisible(false) }, }) - const canInviteUsers = useCheckPermissions(PermissionAction.AUTH_EXECUTE, 'invite_user') + const { can: canInviteUsers } = useAsyncCheckProjectPermissions( + PermissionAction.AUTH_EXECUTE, + 'invite_user' + ) const validate = (values: any) => { const errors: any = {} diff --git a/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx index 0cb86332ff9eb..6a83f3c58fc1e 100644 --- a/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx @@ -15,7 +15,7 @@ import { useUserSendMagicLinkMutation } from 'data/auth/user-send-magic-link-mut import { useUserSendOTPMutation } from 'data/auth/user-send-otp-mutation' import { useUserUpdateMutation } from 'data/auth/user-update-mutation' import { User } from 'data/auth/users-infinite-query' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { BASE_PATH } from 'lib/constants' import { timeout } from 'lib/helpers' import { Button, cn, Separator } from 'ui' @@ -59,12 +59,24 @@ export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => { } ) - const canUpdateUser = useCheckPermissions(PermissionAction.AUTH_EXECUTE, '*') - const canSendMagicLink = useCheckPermissions(PermissionAction.AUTH_EXECUTE, 'send_magic_link') - const canSendRecovery = useCheckPermissions(PermissionAction.AUTH_EXECUTE, 'send_recovery') - const canSendOtp = useCheckPermissions(PermissionAction.AUTH_EXECUTE, 'send_otp') - const canRemoveUser = useCheckPermissions(PermissionAction.TENANT_SQL_DELETE, 'auth.users') - const canRemoveMFAFactors = useCheckPermissions( + const { can: canUpdateUser } = useAsyncCheckProjectPermissions(PermissionAction.AUTH_EXECUTE, '*') + const { can: canSendMagicLink } = useAsyncCheckProjectPermissions( + PermissionAction.AUTH_EXECUTE, + 'send_magic_link' + ) + const { can: canSendRecovery } = useAsyncCheckProjectPermissions( + PermissionAction.AUTH_EXECUTE, + 'send_recovery' + ) + const { can: canSendOtp } = useAsyncCheckProjectPermissions( + PermissionAction.AUTH_EXECUTE, + 'send_otp' + ) + const { can: canRemoveUser } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_DELETE, + 'auth.users' + ) + const { can: canRemoveMFAFactors } = useAsyncCheckProjectPermissions( PermissionAction.TENANT_SQL_DELETE, 'auth.mfa_factors' ) diff --git a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx index 5e693553554be..85d315f7ef780 100644 --- a/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx +++ b/apps/studio/components/interfaces/Database/Replication/DestinationRow.tsx @@ -5,7 +5,7 @@ import { toast } from 'sonner' import { useParams } from 'common' import Table from 'components/to-be-cleaned/Table' import AlertError from 'components/ui/AlertError' -import { useDeleteDestinationMutation } from 'data/replication/delete-destination-mutation' +import { useDeleteDestinationPipelineMutation } from 'data/replication/delete-destination-pipeline-mutation' import { useReplicationPipelineStatusQuery } from 'data/replication/pipeline-status-query' import { Pipeline } from 'data/replication/pipelines-query' import { useStopPipelineMutation } from 'data/replication/stop-pipeline-mutation' @@ -69,7 +69,7 @@ export const DestinationRow = ({ : PipelineStatusRequestStatus.None const { mutateAsync: stopPipeline } = useStopPipelineMutation() - const { mutateAsync: deleteDestination } = useDeleteDestinationMutation({}) + const { mutateAsync: deleteDestinationPipeline } = useDeleteDestinationPipelineMutation({}) const pipelineStatus = pipelineStatusData?.status const statusName = getStatusName(pipelineStatus) @@ -84,9 +84,11 @@ export const DestinationRow = ({ try { await stopPipeline({ projectRef, pipelineId: pipeline.id }) - // deleting the destination also deletes the pipeline because of cascade delete - // so we don't need to call deletePipeline explicitly - await deleteDestination({ projectRef, destinationId: destinationId }) + await deleteDestinationPipeline({ + projectRef, + destinationId: destinationId, + pipelineId: pipeline.id, + }) } catch (error) { toast.error(PIPELINE_ERROR_MESSAGES.DELETE_DESTINATION) } diff --git a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/create-key-dialog.tsx b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/create-key-dialog.tsx index 639fbec988627..a3151640a4ce3 100644 --- a/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/create-key-dialog.tsx +++ b/apps/studio/components/interfaces/JwtSecrets/jwt-secret-keys-table/create-key-dialog.tsx @@ -1,5 +1,4 @@ import dayjs from 'dayjs' -import relativeTime from 'dayjs/plugin/relativeTime' import { useMemo, useState } from 'react' import { toast } from 'sonner' @@ -24,8 +23,6 @@ import { Textarea, } from 'ui' -dayjs.extend(relativeTime) - const RSA_JWK_REQUIRED_PROPERTIES = ['kty', 'n', 'e', 'p', 'q', 'd', 'dq', 'dp', 'qi'] const EC_JWK_REQUIRED_PROPERTIES = ['kty', 'crv', 'x', 'y', 'd'] const ALLOWED_JWK_PROPERTIES = new Set([ diff --git a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx index e9bd8a54ec0a2..f43ac43e38a85 100644 --- a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx @@ -21,7 +21,7 @@ import { } from 'data/table-editor/table-editor-types' import { useTableUpdateMutation } from 'data/tables/table-update-mutation' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' -import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' @@ -107,9 +107,13 @@ const GridHeaderActions = ({ table }: GridHeaderActionsProps) => { }, }) - const canSqlWriteTables = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') - const canSqlWriteColumns = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns') - const isReadOnly = !canSqlWriteTables && !canSqlWriteColumns + const { can: canSqlWriteTables, isLoading: isLoadingPermissions } = + useAsyncCheckProjectPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') + const { can: canSqlWriteColumns } = useAsyncCheckProjectPermissions( + PermissionAction.TENANT_SQL_ADMIN_WRITE, + 'columns' + ) + const isReadOnly = !isLoadingPermissions && !canSqlWriteTables && !canSqlWriteColumns // This will change when we allow autogenerated API docs for schemas other than `public` const doesHaveAutoGeneratedAPIDocs = table.schema === 'public' diff --git a/apps/studio/components/layouts/Tabs/RecentItems.tsx b/apps/studio/components/layouts/Tabs/RecentItems.tsx index bc684c59709ea..c7dd4276bc97b 100644 --- a/apps/studio/components/layouts/Tabs/RecentItems.tsx +++ b/apps/studio/components/layouts/Tabs/RecentItems.tsx @@ -1,5 +1,4 @@ import dayjs from 'dayjs' -import relativeTime from 'dayjs/plugin/relativeTime' import { AnimatePresence, motion } from 'framer-motion' import Link from 'next/link' @@ -9,8 +8,6 @@ import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' import { editorEntityTypes, useTabsStateSnapshot } from 'state/tabs' import { useEditorType } from '../editors/EditorsLayout.hooks' -dayjs.extend(relativeTime) - export function RecentItems() { const { ref } = useParams() const tabs = useTabsStateSnapshot() diff --git a/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx b/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx index 4072f6f2caab9..81cefd8d75a72 100644 --- a/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx +++ b/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.tsx @@ -1,6 +1,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { JwtSecretUpdateStatus } from '@supabase/shared-types/out/events' import { AlertCircle, Loader2 } from 'lucide-react' +import Link from 'next/link' import { useMemo } from 'react' import { toast } from 'sonner' @@ -9,8 +10,6 @@ import Panel from 'components/ui/Panel' import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' -import { useFlag } from 'hooks/ui/useFlag' -import Link from 'next/link' import { Input } from 'ui' import { getLastUsedAPIKeys, useLastUsedAPIKeysLogQuery } from './DisplayApiSettings.utils' import { ToggleLegacyApiKeysPanel } from './ToggleLegacyApiKeys' @@ -26,8 +25,6 @@ export const DisplayApiSettings = ({ }) => { const { ref: projectRef } = useParams() - const newApiKeysInRollOut = useFlag('basicApiKeys') - const { data: settings, isError: isProjectSettingsError, @@ -217,31 +214,20 @@ export const DisplayApiSettings = ({ )) )} {showNotice ? ( - newApiKeysInRollOut ? ( - - ) : ( - - ) - ) : null} + href="https://github.com/orgs/supabase/discussions/29260" + buttonText="Read the announcement" + /> + ) : ( + + )} - {newApiKeysInRollOut && !showNotice && } ) } diff --git a/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.utils.ts b/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.utils.ts index b8bd899e9bb4b..ae9fc2d3b8c96 100644 --- a/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.utils.ts +++ b/apps/studio/components/ui/ProjectSettings/DisplayApiSettings.utils.ts @@ -1,13 +1,8 @@ import dayjs from 'dayjs' -import duration from 'dayjs/plugin/duration' -import relativeTime from 'dayjs/plugin/relativeTime' import { useRef } from 'react' import useLogsQuery from 'hooks/analytics/useLogsQuery' -dayjs.extend(duration) -dayjs.extend(relativeTime) - export function useLastUsedAPIKeysLogQuery(projectRef: string) { const now = useRef(new Date()).current return useLogsQuery(projectRef, { diff --git a/apps/studio/data/replication/delete-destination-mutation.ts b/apps/studio/data/replication/delete-destination-mutation.ts deleted file mode 100644 index ce17893e5c47d..0000000000000 --- a/apps/studio/data/replication/delete-destination-mutation.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' -import { toast } from 'sonner' - -import type { ResponseError } from 'types' -import { replicationKeys } from './keys' -import { handleError, del } from 'data/fetchers' - -export type DeleteDestinationParams = { - projectRef: string - destinationId: number -} - -async function deleteDestination( - { projectRef, destinationId: destinationId }: DeleteDestinationParams, - signal?: AbortSignal -) { - if (!projectRef) throw new Error('projectRef is required') - - const { data, error } = await del('/platform/replication/{ref}/destinations/{destination_id}', { - params: { path: { ref: projectRef, destination_id: destinationId } }, - signal, - }) - if (error) { - handleError(error) - } - - return data -} - -type DeleteDestinationData = Awaited> - -export const useDeleteDestinationMutation = ({ - onSuccess, - onError, - ...options -}: Omit< - UseMutationOptions, - 'mutationFn' -> = {}) => { - const queryClient = useQueryClient() - - return useMutation( - (vars) => deleteDestination(vars), - { - async onSuccess(data, variables, context) { - const { projectRef, destinationId } = variables - await queryClient.invalidateQueries(replicationKeys.destinations(projectRef)) - await onSuccess?.(data, variables, context) - }, - async onError(data, variables, context) { - if (onError === undefined) { - toast.error(`Failed to delete destination: ${data.message}`) - } else { - onError(data, variables, context) - } - }, - ...options, - } - ) -} diff --git a/apps/studio/data/replication/delete-destination-pipeline-mutation.ts b/apps/studio/data/replication/delete-destination-pipeline-mutation.ts new file mode 100644 index 0000000000000..2ccb247342311 --- /dev/null +++ b/apps/studio/data/replication/delete-destination-pipeline-mutation.ts @@ -0,0 +1,64 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { toast } from 'sonner' + +import type { ResponseError } from 'types' +import { replicationKeys } from './keys' +import { handleError, del } from 'data/fetchers' + +export type DeleteDestinationPipelineParams = { + projectRef: string + destinationId: number + pipelineId: number +} + +async function deleteDestinationPipeline( + { projectRef, destinationId, pipelineId }: DeleteDestinationPipelineParams, + signal?: AbortSignal +) { + if (!projectRef) throw new Error('projectRef is required') + + const { data, error } = await del( + '/platform/replication/{ref}/destinations-pipelines/{destination_id}/{pipeline_id}', + { + params: { path: { ref: projectRef, destination_id: destinationId, pipeline_id: pipelineId } }, + signal, + } + ) + if (error) { + handleError(error) + } + + return data +} + +type DeleteDestinationPipelineData = Awaited> + +export const useDeleteDestinationPipelineMutation = ({ + onSuccess, + onError, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => deleteDestinationPipeline(vars), + { + async onSuccess(data, variables, context) { + const { projectRef } = variables + await queryClient.invalidateQueries(replicationKeys.destinations(projectRef)) + await onSuccess?.(data, variables, context) + }, + async onError(data, variables, context) { + if (onError === undefined) { + toast.error(`Failed to delete destination and pipeline: ${data.message}`) + } else { + onError(data, variables, context) + } + }, + ...options, + } + ) +} diff --git a/apps/studio/hooks/misc/useCheckPermissions.ts b/apps/studio/hooks/misc/useCheckPermissions.ts index b77782fbda519..9ebf33696fb00 100644 --- a/apps/studio/hooks/misc/useCheckPermissions.ts +++ b/apps/studio/hooks/misc/useCheckPermissions.ts @@ -134,7 +134,10 @@ export function useGetProjectPermissions( } /** - * @deprecated Use useAsyncCheckProjectPermissions instead + * @deprecated If checking for project permissions, use useAsyncCheckProjectPermissions instead so that we can always + * check for loading states to not prematurely show "no perms" UIs. We'll also need a separate async check for org perms too + * + * Use `import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'` instead */ export function useCheckPermissions( action: string, diff --git a/apps/studio/pages/_app.tsx b/apps/studio/pages/_app.tsx index b062e6840535c..edeb124fc6f6a 100644 --- a/apps/studio/pages/_app.tsx +++ b/apps/studio/pages/_app.tsx @@ -24,6 +24,7 @@ import { Hydrate, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import dayjs from 'dayjs' import customParseFormat from 'dayjs/plugin/customParseFormat' +import duration from 'dayjs/plugin/duration' import relativeTime from 'dayjs/plugin/relativeTime' import timezone from 'dayjs/plugin/timezone' import utc from 'dayjs/plugin/utc' @@ -56,6 +57,7 @@ dayjs.extend(customParseFormat) dayjs.extend(utc) dayjs.extend(timezone) dayjs.extend(relativeTime) +dayjs.extend(duration) loader.config({ // [Joshen] Attempt for offline support/bypass ISP issues is to store the assets required for monaco diff --git a/apps/studio/pages/new/[slug].tsx b/apps/studio/pages/new/[slug].tsx index 84301d2040aae..311c60f67c9ad 100644 --- a/apps/studio/pages/new/[slug].tsx +++ b/apps/studio/pages/new/[slug].tsx @@ -90,18 +90,7 @@ import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { InfoTooltip } from 'ui-patterns/info-tooltip' -const sizes: DesiredInstanceSize[] = [ - 'micro', - 'small', - 'medium', - 'large', - 'xlarge', - '2xlarge', - '4xlarge', - '8xlarge', - '12xlarge', - '16xlarge', -] +const sizes: DesiredInstanceSize[] = ['micro', 'small', 'medium'] const sizesWithNoCostConfirmationRequired: DesiredInstanceSize[] = ['micro', 'small'] @@ -783,7 +772,7 @@ const Wizard: NextPageWithLayout = () => { CPU

${instanceSizeSpecs[option].priceHourly}/hour (~$ @@ -794,6 +783,15 @@ const Wizard: NextPageWithLayout = () => { ) })} + +

+ Larger instance sizes available after creation +
+ diff --git a/apps/studio/pages/project/[ref]/auth/providers.tsx b/apps/studio/pages/project/[ref]/auth/providers.tsx index 57840283ae402..2bdc9ab95848b 100644 --- a/apps/studio/pages/project/[ref]/auth/providers.tsx +++ b/apps/studio/pages/project/[ref]/auth/providers.tsx @@ -1,21 +1,10 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' - import { AuthProvidersForm, BasicAuthSettingsForm } from 'components/interfaces/Auth' 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 { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' const ProvidersPage: NextPageWithLayout = () => { - const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') - const isPermissionsLoaded = usePermissionsLoaded() - - if (isPermissionsLoaded && !canReadAuthSettings) { - return - } - return ( diff --git a/apps/studio/pages/project/[ref]/settings/api-keys/new.tsx b/apps/studio/pages/project/[ref]/settings/api-keys/new.tsx index 9ceadc7cba2d4..99e475e2c9d52 100644 --- a/apps/studio/pages/project/[ref]/settings/api-keys/new.tsx +++ b/apps/studio/pages/project/[ref]/settings/api-keys/new.tsx @@ -1,5 +1,4 @@ import { - ApiKeysComingSoonBanner, ApiKeysCreateCallout, ApiKeysFeedbackBanner, } from 'components/interfaces/APIKeys/ApiKeysIllustrations' @@ -14,11 +13,10 @@ import type { NextPageWithLayout } from 'types' import { Separator } from 'ui' const ApiKeysNewPage: NextPageWithLayout = () => { - const { isInRollout, shouldDisableUI, canInitApiKeys } = useApiKeysVisibility() + const { shouldDisableUI, canInitApiKeys } = useApiKeysVisibility() return ( <> - {!isInRollout && } {canInitApiKeys && } diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index 0e606ba3d3233..b84082af171d7 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -3434,7 +3434,8 @@ export interface paths { put?: never /** Update a replication destination and pipeline. */ post: operations['ReplicationDestinationsPipelinesController_updateDestinationPipeline'] - delete?: never + /** Delete a replication destination and pipeline. */ + delete: operations['ReplicationDestinationsPipelinesController_deleteDestinationPipeline'] options?: never head?: never patch?: never @@ -4808,6 +4809,8 @@ export interface components { } | { billing_email: string | null + /** @enum {string|null} */ + billing_partner: 'fly' | 'aws' | 'vercel_marketplace' | null id: number is_owner: boolean name: string @@ -6582,6 +6585,8 @@ export interface components { } OrganizationResponse: { billing_email: string | null + /** @enum {string|null} */ + billing_partner: 'fly' | 'aws' | 'vercel_marketplace' | null id: number is_owner: boolean name: string @@ -6634,9 +6639,8 @@ export interface components { } OrganizationSlugResponse: { billing_email: string | null - billing_metadata: { - [key: string]: unknown - } | null + /** @enum {string|null} */ + billing_partner: 'fly' | 'aws' | 'vercel_marketplace' | null has_oriole_project: boolean id: number name: string @@ -19126,6 +19130,44 @@ export interface operations { } } } + ReplicationDestinationsPipelinesController_deleteDestinationPipeline: { + parameters: { + query?: never + header?: never + path: { + /** @description Destination id */ + destination_id: number + /** @description Pipeline id */ + pipeline_id: number + /** @description Project reference */ + ref: string + } + cookie?: never + } + requestBody?: never + responses: { + /** @description Returned when the replication destination and pipeline are deleted. */ + 201: { + headers: { + [name: string]: unknown + } + content?: never + } + 403: { + headers: { + [name: string]: unknown + } + content?: never + } + /** @description Returned when the API fails to delete the replication destination or pipeline. */ + 500: { + headers: { + [name: string]: unknown + } + content?: never + } + } + } ReplicationDestinationsController_getDestination: { parameters: { query?: never