From 84fb27a795bf32988aed41421b3d42ff2ba35272 Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Tue, 30 Sep 2025 23:06:46 -0600 Subject: [PATCH 1/9] docs: add mcp to feature status page (#39015) --- apps/docs/content/guides/getting-started/features.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/content/guides/getting-started/features.mdx b/apps/docs/content/guides/getting-started/features.mdx index a2dd8b8bf4912..33cf6c0345bbc 100644 --- a/apps/docs/content/guides/getting-started/features.mdx +++ b/apps/docs/content/guides/getting-started/features.mdx @@ -212,6 +212,7 @@ In addition to the Beta requirements, features in GA are covered by the [uptime | Platform | Terraform Provider | `public alpha` | N/A | | Platform | Read Replicas | `GA` | N/A | | Platform | Log Drains | `public alpha` | ✅ | +| Platform | MCP | `public alpha` | ✅ | | Studio | | `GA` | ✅ | | Studio | SSO | `GA` | ✅ | | Studio | Column Privileges | `public alpha` | ✅ | From 715006c4a18ae7ab5c41a20a9af5cd60009db8d2 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Wed, 1 Oct 2025 13:51:33 +0800 Subject: [PATCH 2/9] chore: hide edge function examples (#39142) * chore: hide edge function examples * Nit --------- Co-authored-by: Joshen Lim --- .../EdgeFunctionDetails/EdgeFunctionDetails.tsx | 13 +++++++++++-- .../common/enabled-features/enabled-features.json | 1 + .../enabled-features/enabled-features.schema.json | 4 ++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx index 74aab28e1c10c..2d3f39195b068 100644 --- a/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx +++ b/apps/studio/components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails.tsx @@ -19,6 +19,7 @@ import { useEdgeFunctionQuery } from 'data/edge-functions/edge-function-query' import { useEdgeFunctionDeleteMutation } from 'data/edge-functions/edge-functions-delete-mutation' import { useEdgeFunctionUpdateMutation } from 'data/edge-functions/edge-functions-update-mutation' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { DOCS_URL } from 'lib/constants' import { Alert_Shadcn_, @@ -65,6 +66,14 @@ export const EdgeFunctionDetails = () => { '*' ) + const showAllEdgeFunctionInvocationExamples = useIsFeatureEnabled( + 'edge_functions:show_all_edge_function_invocation_examples' + ) + const invocationTabs = useMemo(() => { + if (showAllEdgeFunctionInvocationExamples) return INVOCATION_TABS + return INVOCATION_TABS.filter((tab) => tab.id === 'curl' || tab.id === 'supabase-js') + }, [showAllEdgeFunctionInvocationExamples]) + const { data: apiKeys } = useAPIKeysQuery({ projectRef }) const { data: settings } = useProjectSettingsV2Query({ projectRef }) const { data: customDomainData } = useCustomDomainsQuery({ projectRef }) @@ -228,13 +237,13 @@ export const EdgeFunctionDetails = () => { - {INVOCATION_TABS.map((tab) => ( + {invocationTabs.map((tab) => ( {tab.label} ))} - {INVOCATION_TABS.map((tab) => ( + {invocationTabs.map((tab) => (
Date: Wed, 1 Oct 2025 14:03:56 +0800 Subject: [PATCH 3/9] chore: disable client libraries in support form (#39143) * chore: disable client libraries in support form * Nit --------- Co-authored-by: Joshen Lim --- .../interfaces/Support/SupportFormV2.tsx | 46 ++++++++++++------- .../enabled-features/enabled-features.json | 4 +- .../enabled-features.schema.json | 5 ++ 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/apps/studio/components/interfaces/Support/SupportFormV2.tsx b/apps/studio/components/interfaces/Support/SupportFormV2.tsx index 891d344b5f3ed..7de9cf432ef87 100644 --- a/apps/studio/components/interfaces/Support/SupportFormV2.tsx +++ b/apps/studio/components/interfaces/Support/SupportFormV2.tsx @@ -1,5 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod' import * as Sentry from '@sentry/nextjs' +import { SupportCategories } from '@supabase/shared-types/out/constants' import { AnimatePresence, motion } from 'framer-motion' import { Book, @@ -15,12 +16,12 @@ import { } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' +import { useQueryState } from 'nuqs' import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' -import { SupportCategories } from '@supabase/shared-types/out/constants' import { useDocsSearch, useParams, type DocsSearchResult as Page } from 'common' import { CLIENT_LIBRARIES } from 'common/constants' import CopyButton from 'components/ui/CopyButton' @@ -34,7 +35,6 @@ import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { DOCS_URL } from 'lib/constants' import { detectBrowser } from 'lib/helpers' import { useProfile } from 'lib/profile' -import { useQueryState } from 'nuqs' import { Badge, Button, @@ -78,8 +78,8 @@ const MAX_ATTACHMENTS = 5 const INCLUDE_DISCUSSIONS = ['Problem', 'Database_unresponsive'] const CONTAINER_CLASSES = 'px-6' -const FormSchema = z - .object({ +const createFormSchema = (showClientLibraries: boolean) => { + const baseSchema = z.object({ organizationSlug: z.string().min(1, 'Please select an organization'), projectRef: z.string().min(1, 'Please select a project'), category: z.string().min(1, 'Please select an issue type'), @@ -90,15 +90,24 @@ const FormSchema = z affectedServices: z.string(), allowSupportAccess: z.boolean(), }) - .refine( - (data) => { - return !(data.category === 'Problem' && data.library === '') - }, - { - message: "Please select the library that you're facing issues with", - path: ['library'], - } - ) + + if (showClientLibraries) { + return baseSchema.refine( + (data) => { + return !(data.category === 'Problem' && data.library === '') + }, + { + message: "Please select the library that you're facing issues with", + path: ['library'], + } + ) + } + + // When showClientLibraries is false, make library optional and remove the refine validation + return baseSchema.extend({ + library: z.string().optional(), + }) +} const defaultValues = { organizationSlug: '', @@ -142,6 +151,7 @@ export const SupportFormV2 = ({ const dashboardSentryIssueId = router.query.sid as string const isBillingEnabled = useIsFeatureEnabled('billing:all') + const showClientLibraries = useIsFeatureEnabled('support:show_client_libraries') const categoryOptions = useMemo(() => { return CATEGORY_OPTIONS.filter((option) => { @@ -162,6 +172,8 @@ export const SupportFormV2 = ({ const [uploadedFiles, setUploadedFiles] = useState([]) const [uploadedDataUrls, setUploadedDataUrls] = useState([]) + const FormSchema = useMemo(() => createFormSchema(showClientLibraries), [showClientLibraries]) + const form = useForm>({ mode: 'onBlur', reValidateMode: 'onBlur', @@ -244,7 +256,9 @@ export const SupportFormV2 = ({ setIsSubmitting(true) const attachments = uploadedFiles.length > 0 ? await uploadAttachments(values.projectRef, uploadedFiles) : [] - const selectedLibrary = CLIENT_LIBRARIES.find((library) => library.language === values.library) + const selectedLibrary = values.library + ? CLIENT_LIBRARIES.find((library) => library.language === values.library) + : undefined const payload = { ...values, @@ -699,7 +713,7 @@ export const SupportFormV2 = ({ )}
- {category === 'Problem' && ( + {category === 'Problem' && showClientLibraries && ( )} - {library.length > 0 && } + {library && library.length > 0 && } {category !== 'Login_issues' && ( Date: Wed, 1 Oct 2025 05:08:26 -0300 Subject: [PATCH 4/9] docs: add Flutter documentation for maxAffected and linkIdentityWithIdToken (#39132) --- .../guides/auth/auth-identity-linking.mdx | 57 +++++++ apps/docs/spec/supabase_dart_v2.yml | 154 ++++++++++++++++++ 2 files changed, 211 insertions(+) diff --git a/apps/docs/content/guides/auth/auth-identity-linking.mdx b/apps/docs/content/guides/auth/auth-identity-linking.mdx index aa8a98b22ec44..fe950304c1d5a 100644 --- a/apps/docs/content/guides/auth/auth-identity-linking.mdx +++ b/apps/docs/content/guides/auth/auth-identity-linking.mdx @@ -90,6 +90,63 @@ response = supabase.auth.link_identity({'provider': 'google'}) In the example above, the user will be redirected to Google to complete the OAuth2.0 flow. Once the OAuth2.0 flow has completed successfully, the user will be redirected back to the application and the Google identity will be linked to the user. You can enable manual linking from your project's authentication [configuration options](/dashboard/project/_/auth/providers) or by setting the environment variable `GOTRUE_SECURITY_MANUAL_LINKING_ENABLED: true` when self-hosting. +### Link identity with native OAuth (ID token) + + + + +For native mobile applications, you can link an identity using an ID token obtained from a third-party OAuth provider. This is useful when you want to use native OAuth flows (like Google Sign-In or Sign in with Apple) rather than web-based OAuth redirects. + +```js +// Example with Google Sign-In (using a native Google Sign-In library) +const idToken = 'ID_TOKEN_FROM_GOOGLE' +const accessToken = 'ACCESS_TOKEN_FROM_GOOGLE' + +const { data, error } = await supabase.auth.linkIdentityWithIdToken({ + provider: 'google', + token: idToken, + access_token: accessToken, +}) +``` + + +<$Show if="sdk:dart"> + + +For Flutter applications, you can link an identity using an ID token obtained from native OAuth packages like `google_sign_in` or `sign_in_with_apple`. Call [`linkIdentityWithIdToken()`](/docs/reference/dart/auth-linkidentitywithidtoken): + +```dart +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +// First, obtain the ID token from the native provider +final GoogleSignIn googleSignIn = GoogleSignIn( + clientId: iosClientId, + serverClientId: webClientId, +); +final googleUser = await googleSignIn.signIn(); +final googleAuth = await googleUser!.authentication; + +// Link the Google identity to the current user +final response = await supabase.auth.linkIdentityWithIdToken( + provider: OAuthProvider.google, + idToken: googleAuth.idToken!, + accessToken: googleAuth.accessToken!, +); +``` + +This method supports the same OAuth providers as `signInWithIdToken()`: Google, Apple, Facebook, Kakao, and Keycloak. + + + + + ## Unlink an identity Date: Wed, 1 Oct 2025 05:08:39 -0300 Subject: [PATCH 5/9] docs(swift): add documentation for new Swift SDK features (#39133) --- apps/docs/spec/supabase_swift_v2.yml | 87 ++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/apps/docs/spec/supabase_swift_v2.yml b/apps/docs/spec/supabase_swift_v2.yml index b819da8f36b18..ff6477548e808 100644 --- a/apps/docs/spec/supabase_swift_v2.yml +++ b/apps/docs/spec/supabase_swift_v2.yml @@ -931,6 +931,42 @@ functions: // custom URL opening logic } ``` + - id: link-identity-with-id-token + title: 'linkIdentityWithIdToken()' + notes: | + - The **Enable Manual Linking** option must be enabled from your [project's authentication settings](/dashboard/project/_/settings/auth). + - The user needs to be signed in to call `linkIdentityWithIdToken()`. + - If the candidate identity is already linked to the existing user or another user, `linkIdentityWithIdToken()` will fail. + - This method allows you to link an OIDC identity using an ID token obtained from a provider like Apple, Google, etc. + examples: + - id: link-identity-with-id-token + name: Link an OIDC identity using an ID token + isSpotlight: true + code: | + ```swift + try await supabase.auth.linkIdentityWithIdToken( + credentials: OpenIDConnectCredentials( + provider: .apple, + idToken: idToken + ) + ) + ``` + description: | + Link an OpenID Connect identity to the current user using an ID token. This is useful when you've obtained an ID token from a provider's SDK (like Sign in with Apple) and want to link it to the current user's account. + - id: link-identity-with-id-token-and-nonce + name: Link an OIDC identity with nonce + code: | + ```swift + try await supabase.auth.linkIdentityWithIdToken( + credentials: OpenIDConnectCredentials( + provider: .apple, + idToken: idToken, + nonce: nonce + ) + ) + ``` + description: | + For providers that support nonce verification (like Apple), you can include the nonce used during authentication. - id: unlink-identity title: 'unlinkIdentity()' notes: | @@ -3750,6 +3786,57 @@ functions: hideCodeBlock: true isSpotlight: true + - id: max-affected + title: maxAffected() + description: | + Set the maximum number of rows that can be affected by the query. + + Only available in PostgREST v13+ and only works with PATCH, DELETE methods and RPC calls. + notes: | + - This method sets `handling=strict` and `max-affected=` headers for row limit enforcement. + - Only use with UPDATE (PATCH), DELETE operations, or RPC calls that modify data. + - If the query would affect more rows than specified, PostgREST will return an error. + - This is useful for preventing accidental bulk updates or deletes. + examples: + - id: with-update + name: With `update()` + code: | + ```swift + try await supabase + .from("users") + .update(["status": "active"]) + .eq("id", value: 1) + .maxAffected(1) + .execute() + ``` + description: | + Ensure that only one row is updated. If the query would update more than one row, an error is returned. + isSpotlight: true + - id: with-delete + name: With `delete()` + code: | + ```swift + try await supabase + .from("users") + .delete() + .in("id", values: [1, 2, 3]) + .maxAffected(3) + .execute() + ``` + description: | + Ensure that only three rows are deleted. If the query would delete more than three rows, an error is returned. + - id: with-rpc + name: With `rpc()` + code: | + ```swift + try await supabase + .rpc("delete_inactive_users") + .maxAffected(10) + .execute() + ``` + description: | + Ensure that the RPC call affects at most 10 rows. Useful for limiting the impact of stored procedures. + - id: single title: single() description: | From 2ec278a02260afb11518c6a4726555dda15b2a19 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Wed, 1 Oct 2025 05:08:51 -0300 Subject: [PATCH 6/9] docs: document automatic presence enablement in Python client (#39129) --- apps/docs/spec/supabase_py_v2.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/spec/supabase_py_v2.yml b/apps/docs/spec/supabase_py_v2.yml index 31f21f040112f..137014918b669 100644 --- a/apps/docs/spec/supabase_py_v2.yml +++ b/apps/docs/spec/supabase_py_v2.yml @@ -6832,6 +6832,7 @@ functions: title: on().subscribe() notes: | - By default, Broadcast and Presence are enabled for all projects. + - Presence is automatically enabled when you attach presence callbacks (`on_presence_sync()`, `on_presence_join()`, or `on_presence_leave()`). If you add presence callbacks to an already joined channel, the channel will automatically resubscribe with presence enabled. - By default, listening to database changes is disabled for new projects due to database performance and security concerns. You can turn it on by managing Realtime's [replication](/docs/guides/api#realtime-api-overview). - You can receive the "previous" data for updates and deletes by setting the table's `REPLICA IDENTITY` to `FULL` (e.g., `ALTER TABLE your_table REPLICA IDENTITY FULL;`). - Row level security is not applied to delete statements. When RLS is enabled and replica identity is set to full, only the primary key is sent to clients. From 69e346d4301ab2442c6f4a9430b0379de0f3dc68 Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Wed, 1 Oct 2025 05:09:09 -0300 Subject: [PATCH 7/9] docs: add max_affected() method to Python client reference (#39128) --- apps/docs/spec/supabase_py_v2.yml | 92 +++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/apps/docs/spec/supabase_py_v2.yml b/apps/docs/spec/supabase_py_v2.yml index 137014918b669..2798e55e69817 100644 --- a/apps/docs/spec/supabase_py_v2.yml +++ b/apps/docs/spec/supabase_py_v2.yml @@ -6076,6 +6076,98 @@ functions: } ``` hideCodeBlock: true + - id: max-affected + title: max_affected() + description: | + Set the maximum number of rows that can be affected by an update or delete operation. + If the query would affect more rows than the specified limit, the operation will fail with an error. + + This uses PostgREST's `Prefer: max-affected` header with `handling=strict`. + notes: | + - Only available in PostgREST v13+ + - Only works with PATCH (update) and DELETE methods + - The operation will fail if it would affect more rows than specified + params: + - name: value + isOptional: false + type: int + description: The maximum number of rows that can be affected + examples: + - id: with-update + name: Limit affected rows on update + code: | + ```python + response = ( + supabase.table("planets") + .update({"name": "Updated"}) + .eq("id", 1) + .max_affected(1) + .execute() + ) + ``` + data: + sql: | + ```sql + create table + planets (id int8 primary key, name text); + + insert into + planets (id, name) + values + (1, 'Mercury'), + (2, 'Earth'); + ``` + response: | + ```json + { + "data": [ + { + "id": 1, + "name": "Updated" + } + ], + "count": null + } + ``` + hideCodeBlock: true + isSpotlight: true + - id: with-delete + name: Limit affected rows on delete + code: | + ```python + response = ( + supabase.table("planets") + .delete() + .eq("id", 1) + .max_affected(1) + .execute() + ) + ``` + data: + sql: | + ```sql + create table + planets (id int8 primary key, name text); + + insert into + planets (id, name) + values + (1, 'Mercury'), + (2, 'Earth'); + ``` + response: | + ```json + { + "data": [ + { + "id": 1, + "name": "Mercury" + } + ], + "count": null + } + ``` + hideCodeBlock: true - id: using-modifiers title: Using Modifiers description: | From f4c8ab62da6579eac775f7fde96a02d91cbe4c18 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Wed, 1 Oct 2025 10:44:25 +0200 Subject: [PATCH 8/9] fix: enabled/disabled indicator for web3 (#38709) --- .../interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx index 65adb7ac94740..024121b862e51 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx @@ -86,7 +86,10 @@ export const AuthProvidersForm = () => { } else if (providerSchema.title === 'Slack (OIDC)') { isActive = authConfig && (authConfig as any)['EXTERNAL_SLACK_OIDC_ENABLED'] } else if (providerSchema.title.includes('Web3')) { - isActive = authConfig && (authConfig as any)['EXTERNAL_WEB3_SOLANA_ENABLED'] + isActive = + authConfig && + ((authConfig as any)['EXTERNAL_WEB3_SOLANA_ENABLED'] || + (authConfig as any)['EXTERNAL_WEB3_ETHEREUM_ENABLED']) } else { isActive = authConfig && From a2f695547ba40623ec61593093844d09610654c5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 1 Oct 2025 17:26:47 +0800 Subject: [PATCH 9/9] Fix auth pages error state consistency and missing loading states (#39092) --- .../Auth/AdvancedAuthSettingsForm.tsx | 35 +++-- .../interfaces/Auth/AuditLogsForm.tsx | 33 ++-- .../AuthProvidersForm/AuthProvidersForm.tsx | 148 +++++++++--------- .../BasicAuthSettingsForm.tsx | 10 +- .../Auth/EmailTemplates/EmailTemplates.tsx | 6 +- .../interfaces/Auth/Hooks/HooksListing.tsx | 26 ++- .../MfaAuthSettingsForm.tsx | 32 +++- .../ProtectionAuthSettingsForm.tsx | 35 +++-- .../interfaces/Auth/RateLimits/RateLimits.tsx | 18 ++- .../Auth/RedirectUrls/RedirectUrls.tsx | 18 +-- .../SessionsAuthSettingsForm.tsx | 35 +++-- .../interfaces/Auth/SiteUrl/SiteUrl.tsx | 29 ++-- .../interfaces/Auth/SmtpForm/SmtpForm.tsx | 16 +- .../pages/project/[ref]/auth/audit-logs.tsx | 12 +- 14 files changed, 273 insertions(+), 180 deletions(-) diff --git a/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx index 26bd90de84dbf..f52b241cb08bf 100644 --- a/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/AdvancedAuthSettingsForm.tsx @@ -7,6 +7,7 @@ import * as z from 'zod' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import { StringNumberOrNull } from 'components/ui/Forms/Form.constants' import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' @@ -16,9 +17,6 @@ import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, Button, Card, CardContent, @@ -28,8 +26,8 @@ import { Form_Shadcn_, Input_Shadcn_, PrePostTab, - WarningIcon, } from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' const FormSchema = z.object({ @@ -55,7 +53,12 @@ export const AdvancedAuthSettingsForm = () => { const [isUpdatingRequestDurationForm, setIsUpdatingRequestDurationForm] = useState(false) const [isUpdatingDatabaseForm, setIsUpdatingDatabaseForm] = useState(false) - const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) + const { + data: authConfig, + error: authConfigError, + isError, + isLoading, + } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() @@ -148,16 +151,26 @@ export const AdvancedAuthSettingsForm = () => { if (isError) { return ( - - - Failed to retrieve auth configuration - {authConfigError.message} - + + + ) } if (!canReadConfig) { - return + return ( + + + + ) + } + + if (isLoading) { + return ( + + + + ) } return ( diff --git a/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx b/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx index b3e9ccded3448..1630654eae40a 100644 --- a/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx +++ b/apps/studio/components/interfaces/Auth/AuditLogsForm.tsx @@ -7,6 +7,7 @@ import { boolean, object } from 'yup' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import { InlineLink } from 'components/ui/InlineLink' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' @@ -14,9 +15,6 @@ import { useTablesQuery } from 'data/tables/tables-query' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, Button, Card, CardContent, @@ -25,9 +23,8 @@ import { FormField_Shadcn_, Form_Shadcn_, Switch, - WarningIcon, } from 'ui' -import { Admonition } from 'ui-patterns' +import { Admonition, GenericSkeletonLoader } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' const schema = object({ @@ -53,7 +50,12 @@ export const AuditLogsForm = () => { }) const auditLogTable = tables.find((x) => x.name === AUDIT_LOG_ENTRIES_TABLE) - const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) + const { + data: authConfig, + error: authConfigError, + isError, + isLoading, + } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation({ onError: (error) => { @@ -85,11 +87,20 @@ export const AuditLogsForm = () => { if (isError) { return ( - - - Failed to retrieve auth configuration - {authConfigError.message} - + + + + ) + } + + if (isLoading) { + return ( + + + ) } diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx index 024121b862e51..178a94789a61c 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx @@ -7,10 +7,10 @@ import { ScaffoldSectionDescription, ScaffoldSectionTitle, } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import { ResourceList } from 'components/ui/Resource/ResourceList' import { HorizontalShimmerWithIcon } from 'components/ui/Shimmers/Shimmers' import { useAuthConfigQuery } from 'data/auth/auth-config-query' -import { DOCS_URL } from 'lib/constants' import { Alert_Shadcn_, AlertDescription_Shadcn_, @@ -39,82 +39,82 @@ export const AuthProvidersForm = () => { Authenticate your users through a suite of providers and login methods -
- {authConfig?.EXTERNAL_EMAIL_ENABLED && authConfig?.MAILER_OTP_EXP > 3600 && ( - - -
- OTP expiry exceeds recommended threshold - -

- We have detected that you have enabled the email provider with the OTP expiry set - to more than an hour. It is recommended to set this value to less than an hour. -

- -
-
-
- )} - - {isLoading && - PROVIDERS_SCHEMAS.map((provider) => ( -
- -
- ))} - {isSuccess && - PROVIDERS_SCHEMAS.map((provider) => { - const providerSchema = - provider.title === 'Phone' - ? { ...provider, validationSchema: getPhoneProviderValidationSchema(authConfig) } - : provider - let isActive = false - if (providerSchema.title === 'SAML 2.0') { - isActive = authConfig && (authConfig as any)['SAML_ENABLED'] - } else if (providerSchema.title === 'LinkedIn (OIDC)') { - isActive = authConfig && (authConfig as any)['EXTERNAL_LINKEDIN_OIDC_ENABLED'] - } else if (providerSchema.title === 'Slack (OIDC)') { - isActive = authConfig && (authConfig as any)['EXTERNAL_SLACK_OIDC_ENABLED'] - } else if (providerSchema.title.includes('Web3')) { - isActive = - authConfig && - ((authConfig as any)['EXTERNAL_WEB3_SOLANA_ENABLED'] || - (authConfig as any)['EXTERNAL_WEB3_ETHEREUM_ENABLED']) - } else { - isActive = - authConfig && - (authConfig as any)[`EXTERNAL_${providerSchema.title.toUpperCase()}_ENABLED`] - } - return ( - - ) - })} - {isError && ( - + {isError ? ( + + ) : ( +
+ {authConfig?.EXTERNAL_EMAIL_ENABLED && authConfig?.MAILER_OTP_EXP > 3600 && ( + - Failed to retrieve auth configuration - - {(authConfigError as any)?.message} - +
+ OTP expiry exceeds recommended threshold + +

+ We have detected that you have enabled the email provider with the OTP expiry + set to more than an hour. It is recommended to set this value to less than an + hour. +

+ +
+
)} - -
+ + + {isLoading && + PROVIDERS_SCHEMAS.map((provider) => ( +
+ +
+ ))} + {isSuccess && + PROVIDERS_SCHEMAS.map((provider) => { + const providerSchema = + provider.title === 'Phone' + ? { + ...provider, + validationSchema: getPhoneProviderValidationSchema(authConfig), + } + : provider + let isActive = false + if (providerSchema.title === 'SAML 2.0') { + isActive = authConfig && (authConfig as any)['SAML_ENABLED'] + } else if (providerSchema.title === 'LinkedIn (OIDC)') { + isActive = authConfig && (authConfig as any)['EXTERNAL_LINKEDIN_OIDC_ENABLED'] + } else if (providerSchema.title === 'Slack (OIDC)') { + isActive = authConfig && (authConfig as any)['EXTERNAL_SLACK_OIDC_ENABLED'] + } else if (providerSchema.title.includes('Web3')) { + isActive = authConfig && (authConfig as any)['EXTERNAL_WEB3_SOLANA_ENABLED'] + } else { + isActive = + authConfig && + (authConfig as any)[`EXTERNAL_${providerSchema.title.toUpperCase()}_ENABLED`] + } + return ( + + ) + })} +
+
+ )} ) } diff --git a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx index 3717cad6b9ed7..3bdb420931859 100644 --- a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx @@ -9,6 +9,7 @@ import { boolean, object, string } from 'yup' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import { InlineLink } from 'components/ui/InlineLink' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' @@ -117,11 +118,10 @@ export const BasicAuthSettingsForm = () => { User Signups {isError && ( - - - Failed to retrieve auth configuration - {authConfigError.message} - + )} {isPermissionsLoaded && !canReadConfig && ( diff --git a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx index 82875d1ff7b09..71b8164e6948e 100644 --- a/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx +++ b/apps/studio/components/interfaces/Auth/EmailTemplates/EmailTemplates.tsx @@ -25,7 +25,11 @@ export const EmailTemplates = () => { return (
{isError && ( - + )} {isLoading && (
diff --git a/apps/studio/components/interfaces/Auth/Hooks/HooksListing.tsx b/apps/studio/components/interfaces/Auth/Hooks/HooksListing.tsx index ac4fc3e093e9c..5a89a957b2468 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/HooksListing.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/HooksListing.tsx @@ -10,6 +10,7 @@ import { useAuthHooksUpdateMutation } from 'data/auth/auth-hooks-update-mutation import { executeSql } from 'data/sql/execute-sql-query' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { cn } from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { AddHookDropdown } from './AddHookDropdown' import { CreateHookSheet } from './CreateHookSheet' @@ -20,7 +21,12 @@ import { extractMethod, getRevokePermissionStatements, isValidHook } from './hoo export const HooksListing = () => { const { ref: projectRef } = useParams() const { data: project } = useSelectedProjectQuery() - const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) + const { + data: authConfig, + error: authConfigError, + isError, + isLoading, + } = useAuthConfigQuery({ projectRef }) const [selectedHook, setSelectedHook] = useState(null) const [selectedHookForDeletion, setSelectedHookForDeletion] = useState(null) @@ -60,10 +66,20 @@ export const HooksListing = () => { if (isError) { return ( - + + + + ) + } + + if (isLoading) { + return ( + + + ) } diff --git a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx index b6bf44c39bc42..1f5cb248e5bc6 100644 --- a/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/MfaAuthSettingsForm/MfaAuthSettingsForm.tsx @@ -7,6 +7,7 @@ import { boolean, number, object, string } from 'yup' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' @@ -15,7 +16,6 @@ import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' import { - AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, @@ -35,6 +35,7 @@ import { Switch, WarningIcon, } from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' @@ -86,7 +87,12 @@ const securitySchema = object({ export const MfaAuthSettingsForm = () => { const { ref: projectRef } = useParams() - const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) + const { + data: authConfig, + error: authConfigError, + isError, + isLoading, + } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() // Separate loading states for each form @@ -253,16 +259,26 @@ export const MfaAuthSettingsForm = () => { if (isError) { return ( - - - Failed to retrieve auth configuration - {authConfigError.message} - + + + ) } if (!canReadConfig) { - return + return ( + + + + ) + } + + if (isLoading) { + return ( + + + + ) } const phoneMFAIsEnabled = diff --git a/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx index f5ef7d711ef1e..63f6674bf8b60 100644 --- a/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/ProtectionAuthSettingsForm/ProtectionAuthSettingsForm.tsx @@ -9,6 +9,7 @@ import { boolean, number, object, string } from 'yup' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import { InlineLink } from 'components/ui/InlineLink' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' @@ -16,9 +17,6 @@ import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutati import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { DOCS_URL } from 'lib/constants' import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, Badge, Button, Card, @@ -35,8 +33,8 @@ import { SelectValue_Shadcn_, Select_Shadcn_, Switch, - WarningIcon, } from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { NO_REQUIRED_CHARACTERS } from '../Auth.constants' @@ -72,7 +70,12 @@ const schema = object({ export const ProtectionAuthSettingsForm = () => { const { ref: projectRef } = useParams() - const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) + const { + data: authConfig, + error: authConfigError, + isError, + isLoading, + } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation({ onError: (error) => { toast.error(`Failed to update settings: ${error?.message}`) @@ -145,16 +148,26 @@ export const ProtectionAuthSettingsForm = () => { if (isError) { return ( - - - Failed to retrieve auth configuration - {authConfigError.message} - + + + ) } if (!canReadConfig) { - return + return ( + + + + ) + } + + if (isLoading) { + return ( + + + + ) } return ( diff --git a/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx b/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx index 82a5a1e14d920..55e086b2a44aa 100644 --- a/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx +++ b/apps/studio/components/interfaces/Auth/RateLimits/RateLimits.tsx @@ -142,15 +142,27 @@ export const RateLimits = () => { }, [isSuccess]) if (isError) { - return + return ( + + + + ) } if (!canReadConfig) { - return + return ( + + + + ) } if (isLoading) { - return + return ( + + + + ) } return ( diff --git a/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx b/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx index 9e4f573f42113..76c31220454fa 100644 --- a/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx +++ b/apps/studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx @@ -7,21 +7,13 @@ import { ScaffoldSection, ScaffoldSectionTitle, } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import { DocsButton } from 'components/ui/DocsButton' import { HorizontalShimmerWithIcon } from 'components/ui/Shimmers/Shimmers' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { DOCS_URL } from 'lib/constants' -import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, - Button, - Modal, - ScrollArea, - WarningIcon, - cn, -} from 'ui' +import { Button, Modal, ScrollArea, cn } from 'ui' import { AddNewURLModal } from './AddNewURLModal' import { RedirectUrlList } from './RedirectUrlList' import { ValueContainer } from './ValueContainer' @@ -98,11 +90,7 @@ export const RedirectUrls = () => { )} {isError && ( - - - Failed to retrieve auth configuration - {authConfigError.message} - + )} {isSuccess && ( diff --git a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx index 3cea989af221f..5e28e21bec217 100644 --- a/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/SessionsAuthSettingsForm/SessionsAuthSettingsForm.tsx @@ -7,6 +7,7 @@ import * as z from 'zod' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import NoPermission from 'components/ui/NoPermission' import UpgradeToPro from 'components/ui/UpgradeToPro' import { useAuthConfigQuery } from 'data/auth/auth-config-query' @@ -15,9 +16,6 @@ import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { IS_PLATFORM } from 'lib/constants' import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, Button, Card, CardContent, @@ -28,8 +26,8 @@ import { Input_Shadcn_, PrePostTab, Switch, - WarningIcon, } from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' function HoursOrNeverText({ value }: { value: number }) { @@ -58,7 +56,12 @@ const UserSessionsSchema = z.object({ export const SessionsAuthSettingsForm = () => { const { ref: projectRef } = useParams() - const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) + const { + data: authConfig, + error: authConfigError, + isError, + isLoading, + } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() // Separate loading states for each form @@ -155,16 +158,26 @@ export const SessionsAuthSettingsForm = () => { if (isError) { return ( - - - Failed to retrieve auth configuration - {authConfigError.message} - + + + ) } if (!canReadConfig) { - return + return ( + + + + ) + } + + if (isLoading) { + return ( + + + + ) } return ( diff --git a/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx b/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx index b1fe49a1f550e..5c38c88261d82 100644 --- a/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx +++ b/apps/studio/components/interfaces/Auth/SiteUrl/SiteUrl.tsx @@ -1,6 +1,5 @@ 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' @@ -8,13 +7,11 @@ import { object, string } from 'yup' import { useParams } from 'common' import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { - AlertDescription_Shadcn_, - AlertTitle_Shadcn_, - Alert_Shadcn_, Button, Card, CardContent, @@ -24,6 +21,7 @@ import { Form_Shadcn_, Input_Shadcn_, } from 'ui' +import { GenericSkeletonLoader } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' const schema = object({ @@ -32,7 +30,12 @@ const schema = object({ const SiteUrl = () => { const { ref: projectRef } = useParams() - const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) + const { + data: authConfig, + error: authConfigError, + isError, + isLoading, + } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() const [isUpdatingSiteUrl, setIsUpdatingSiteUrl] = useState(false) @@ -76,11 +79,17 @@ const SiteUrl = () => { if (isError) { return ( - - - Failed to retrieve auth configuration - {authConfigError.message} - + + + + ) + } + + if (isLoading) { + return ( + + + ) } diff --git a/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx b/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx index 01cf200b2c40a..f8391f8339ce8 100644 --- a/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx +++ b/apps/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx @@ -9,6 +9,7 @@ import * as yup from 'yup' import { useParams } from 'common' import { ScaffoldSection } from 'components/layouts/Scaffold' +import AlertError from 'components/ui/AlertError' import NoPermission from 'components/ui/NoPermission' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' @@ -27,7 +28,6 @@ import { Input_Shadcn_, PrePostTab, Switch, - WarningIcon, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { urlRegex } from '../Auth.constants' @@ -179,16 +179,18 @@ export const SmtpForm = () => { if (isError) { return ( - - - Failed to retrieve auth configuration - {authConfigError.message} - + + + ) } if (!canReadConfig) { - return + return ( + + + + ) } return ( diff --git a/apps/studio/pages/project/[ref]/auth/audit-logs.tsx b/apps/studio/pages/project/[ref]/auth/audit-logs.tsx index e7936c63e30c8..b3ed3be5f2fa4 100644 --- a/apps/studio/pages/project/[ref]/auth/audit-logs.tsx +++ b/apps/studio/pages/project/[ref]/auth/audit-logs.tsx @@ -1,22 +1,18 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { useParams } from 'common' import { AuditLogsForm } from 'components/interfaces/Auth/AuditLogsForm' 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 { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { DOCS_URL } from 'lib/constants' import type { NextPageWithLayout } from 'types' const AuditLogsPage: NextPageWithLayout = () => { - const { ref: projectRef } = useParams() - const { isLoading: isLoadingConfig } = useAuthConfigQuery({ projectRef }) const { can: canReadAuthSettings, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' @@ -28,10 +24,10 @@ const AuditLogsPage: NextPageWithLayout = () => { return ( - {!isPermissionsLoaded || isLoadingConfig ? ( -
+ {!isPermissionsLoaded ? ( + -
+ ) : ( )}