diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1c5b6ea43bc..3eed078f08f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,4 +3,5 @@ /apps/studio/ @supabase/Dashboard /apps/www/ @supabase/Website +/apps/www/_blog /docker/ @supabase/cli diff --git a/.github/workflows/lint-prose.yml b/.github/workflows/lint-prose.yml new file mode 100644 index 00000000000..8e8c46480f8 --- /dev/null +++ b/.github/workflows/lint-prose.yml @@ -0,0 +1,23 @@ +name: reviewdog +on: + pull_request: + paths: + - '.vale.ini' + - 'vale/**' + - 'apps/docs/content/**' + +jobs: + vale: + name: runner / vale + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + sparse-checkout: | + .vale.ini + vale + apps/docs/content + - uses: errata-ai/vale-action@reviewdog + with: + files: apps/docs/content + fail_on_error: true diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 6e3c4d377e4..fde83f5826d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -39,6 +39,8 @@ jobs: run: npx playwright install --with-deps - name: Run Playwright tests run: npm run test:playwright + # mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable. + continue-on-error: true # - uses: actions/upload-artifact@v3 # if: always() # with: diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 00000000000..6b721717743 --- /dev/null +++ b/.vale.ini @@ -0,0 +1,12 @@ +StylesPath = './vale/styles' +MinAlertLevel = 'suggestion' + +Vocab = Supabase + +[formats] +mdx = md + +[*] +BasedOnStyles = Vale, Custom +BlockIgnores = (?s) *(.*?) +TokenIgnores = (href=".*?") diff --git a/apps/database-new/package.json b/apps/database-new/package.json index 8c7e69955d4..c198c0d1858 100644 --- a/apps/database-new/package.json +++ b/apps/database-new/package.json @@ -21,7 +21,7 @@ "dayjs": "^1.11.10", "eslint-config-next": "^14.1.0", "lodash": "^4.17.21", - "lucide-react": "^0.292.0", + "lucide-react": "^0.338.0", "next": "^14.0.3", "openai": "^4.17.0", "react": "^18.2.0", diff --git a/apps/docs/CONTRIBUTING.md b/apps/docs/CONTRIBUTING.md index cc5845c1cec..fa482554221 100644 --- a/apps/docs/CONTRIBUTING.md +++ b/apps/docs/CONTRIBUTING.md @@ -70,7 +70,7 @@ They shouldn't include: ## Repo organization -Most docs pages are contained in the `apps/docs/pages` directory. Some docs sections are federated from other repositories, for example [`pg_graphql`](https://github.com/supabase/pg_graphql/tree/master/docs). Reference docs are generated from spec files in the `spec` directory. +Most docs pages are contained in the `apps/docs/content` directory. Some docs sections are federated from other repositories, for example [`pg_graphql`](https://github.com/supabase/pg_graphql/tree/master/docs). Reference docs are generated from spec files in the `spec` directory. You can usually identify a federated or reference doc because it uses a Next.js dynamic route (for example, `[[...slug]].tsx`). Look for the spec file import or the repo definition to find the content location. @@ -98,43 +98,16 @@ The Supabase docs use [MDX](https://mdxjs.com/). Guides are written in unstructu Adding a new guide requires: -- A layout -- Metadata -- A page export +- YAML frontmatter - A navigation entry (in a separate file) -```js -// required import -import Layout from '~/layouts/DefaultGuideLayout' +Frontmatter looks like this. `title` is mandatory. There are also optional properties that you can use to control the page display, including `subtitle`, `tocVideo`, and `hideToc`. - -/* - * required metadata fields - * title: required - page title - * description: required - page description - * - * optional metadata fields - * id: optional - not currently being used - * subtitle: optional - displayed below the title - * sidebar_label: optional - if you want the sidebar text to be different from the title - * hide_table_of_contents: optional - hides the table of contents on the right side - * tocVideo: optional - a YouTube slug, displays the video above the table of contents - */ -export const meta = { - id: 'nextjs-server-components', - title: 'Supabase Auth with Next.js Server Components', - description: - 'Authentication helpers for creating an authenticated Supabase client in Next.js 13 app directory Server Components.', - sidebar_label: 'Next.js Server Components', -} - -// content -You can use **Markdown syntax**. - -// required export -export const Page = ({ children }) => - -export default Page +```yaml +--- +title: How to connect to Supabase +hideToc: true +--- ``` The navigation is defined in [`NavigationMenu.constants.ts`](https://github.com/supabase/supabase/blob/master/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts). @@ -305,13 +278,12 @@ The `queryGroup` param is optional. It lets you link directly to a tab by using Include videos as TOC (Table of Contents) videos rather than putting them in the main text. -You can define a TOC video in the page metadata: +You can define a TOC video in the page frontmatter: -```js -export const meta = { - ... - tocVideo: 'rzglqRdZUQE', -} +```yaml +--- +tocVideo: 'rzglqRdZUQE', +--- ``` ## Styling, formatting, and grammar diff --git a/apps/docs/components/IPv4DeprecationBanner.tsx b/apps/docs/components/IPv4DeprecationBanner.tsx deleted file mode 100644 index f44662e2069..00000000000 --- a/apps/docs/components/IPv4DeprecationBanner.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import Link from 'next/link' -import { memo } from 'react' - -import { Button, IconExternalLink, cn } from 'ui' - -export const IPv4DeprecationBanner = memo(() => { - return ( -
-

- Prepare for the PgBouncer and IPv4 deprecations on 26th January 2024{' '} - - Learn more - -

-
- -
-
- ) -}) -IPv4DeprecationBanner.displayName = 'IPv4DeprecationBanner' diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index 984db7537a1..525fb496f1e 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -1,13 +1,45 @@ -import { useRouter } from 'next/router' -import { memo, useEffect } from 'react' -import { menuState, useMenuLevelId } from '~/hooks/useMenuState' +import { memo } from 'react' import NavigationMenuHome from './HomeMenu' import NavigationMenuGuideList from './NavigationMenuGuideList' import NavigationMenuRefList from './NavigationMenuRefList' +enum MenuId { + Home = 'home', + GettingStarted = 'gettingstarted', + Database = 'database', + Api = 'api', + Graphql = 'graphql', + Auth = 'auth', + Functions = 'functions', + Realtime = 'realtime', + Storage = 'storage', + Ai = 'ai', + Platform = 'platform', + Resources = 'resources', + SelfHosting = 'self_hosting', + Integrations = 'integrations', + Cli = 'supabase_cli', + RefJavaScriptV1 = 'reference_javascript_v1', + RefJavaScriptV2 = 'reference_javascript_v2', + RefDartV1 = 'reference_dart_v1', + RefDartV2 = 'reference_dart_v2', + RefCSharpV0 = 'reference_csharp_v0', + RefPythonV2 = 'reference_python_v2', + RefSwiftV1 = 'reference_swift_v1', + RefSwiftV2 = 'reference_swift_v2', + RefKotlinV1 = 'reference_kotlin_v1', + RefKotlinV2 = 'reference_kotlin_v2', + RefCli = 'reference_cli', + RefApi = 'reference_api', + SelfHostingAuth = 'reference_self_hosting_auth', + SelfHostingStorage = 'reference_self_hosting_storage', + SelfHostingRealtime = 'reference_self_hosting_realtime', + SelfHostingAnalytics = 'reference_self_hosting_analytics', + SelfHostingFunctions = 'reference_self_hosting_functions', +} + interface BaseMenu { - id: string - path: string + id: MenuId type: string } @@ -21,6 +53,7 @@ interface GuideMenu extends BaseMenu { interface ReferenceMenu extends BaseMenu { type: 'reference' + path: string commonSectionsFile: string specFile?: string } @@ -29,211 +62,181 @@ type Menu = HomeMenu | GuideMenu | ReferenceMenu const menus: Menu[] = [ { - id: 'home', - path: '/', + id: MenuId.Home, type: 'home', }, { - id: 'gettingstarted', - path: '/guides/getting-started', + id: MenuId.GettingStarted, type: 'guide', }, { - id: 'database', - path: '/guides/database', + id: MenuId.Database, type: 'guide', }, { - id: 'api', - path: '/guides/api', + id: MenuId.Api, type: 'guide', }, { - id: 'graphql', - path: '/guides/graphql', + id: MenuId.Graphql, type: 'guide', }, { - id: 'auth', - path: '/guides/auth', + id: MenuId.Auth, type: 'guide', }, { - id: 'auth', - path: '/learn/auth-deep-dive', + id: MenuId.Functions, type: 'guide', }, { - id: 'functions', - path: '/guides/functions', + id: MenuId.Realtime, type: 'guide', }, { - id: 'realtime', - path: '/guides/realtime', + id: MenuId.Storage, type: 'guide', }, { - id: 'storage', - path: '/guides/storage', + id: MenuId.Ai, type: 'guide', }, { - id: 'ai', - path: '/guides/ai', + id: MenuId.Platform, type: 'guide', }, { - id: 'platform', - path: '/guides/platform', + id: MenuId.Resources, type: 'guide', }, { - id: 'resources', - path: '/guides/resources', + id: MenuId.SelfHosting, type: 'guide', }, { - id: 'self_hosting', - path: '/guides/self-hosting', + id: MenuId.Integrations, type: 'guide', }, { - id: 'integrations', - path: '/guides/integrations', + id: MenuId.Cli, type: 'guide', }, { - id: 'supabase_cli', - // TODO: Add path '/guides/cli/config' - path: '/guides/cli', - type: 'guide', - }, - { - id: 'reference_javascript_v1', - path: '/reference/javascript/v1', + id: MenuId.RefJavaScriptV1, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_js_v1.yml', type: 'reference', + path: '/reference/javascript/v1', }, { - id: 'reference_javascript_v2', - path: '/reference/javascript', + id: MenuId.RefJavaScriptV2, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_js_v2.yml', type: 'reference', + path: '/reference/javascript', }, { - id: 'reference_dart_v1', - path: '/reference/dart/v1', + id: MenuId.RefDartV1, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_dart_v1.yml', type: 'reference', + path: '/reference/dart/v1', }, { - id: 'reference_dart_v2', - path: '/reference/dart', + id: MenuId.RefDartV2, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_dart_v2.yml', type: 'reference', + path: '/reference/dart', }, { - id: 'reference_csharp_v0', - path: '/reference/csharp', + id: MenuId.RefCSharpV0, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_csharp_v0.yml', type: 'reference', + path: '/reference/csharp', }, { - id: 'reference_python_v2', - path: '/reference/python', + id: MenuId.RefPythonV2, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_py_v2.yml', type: 'reference', + path: '/reference/python', }, { - id: 'reference_swift_v1', - path: '/reference/swift', + id: MenuId.RefSwiftV1, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_swift_v1.yml', type: 'reference', + path: '/reference/swift', }, { - id: 'reference_swift_v2', - path: '/reference/swift', + id: MenuId.RefSwiftV2, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_swift_v2.yml', type: 'reference', + path: '/reference/swift', }, { - id: 'reference_kotlin_v1', - path: '/reference/kotlin/v1', + id: MenuId.RefKotlinV1, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_kt_v1.yml', type: 'reference', + path: '/reference/kotlin/v1', }, { - id: 'reference_kotlin_v2', - path: '/reference/kotlin', + id: MenuId.RefKotlinV2, commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_kt_v2.yml', type: 'reference', + path: '/reference/kotlin', }, { - id: 'reference_cli', - path: '/reference/cli', + id: MenuId.RefCli, commonSectionsFile: 'common-cli-sections.json', type: 'reference', + path: '/reference/cli', }, { - id: 'reference_api', - path: '/reference/api', + id: MenuId.RefApi, commonSectionsFile: 'common-api-sections.json', type: 'reference', + path: '/reference/api', }, { - id: 'reference_self_hosting_auth', - path: '/reference/self-hosting-auth', + id: MenuId.SelfHostingAuth, commonSectionsFile: 'common-self-hosting-auth-sections.json', type: 'reference', + path: '/reference/self-hosting-auth', }, { - id: 'reference_self_hosting_storage', - path: '/reference/self-hosting-storage', + id: MenuId.SelfHostingStorage, commonSectionsFile: 'common-self-hosting-storage-sections.json', type: 'reference', + path: '/reference/self-hosting-storage', }, { - id: 'reference_self_hosting_realtime', - path: '/reference/self-hosting-realtime', + id: MenuId.SelfHostingRealtime, commonSectionsFile: 'common-self-hosting-realtime-sections.json', type: 'reference', + path: '/reference/self-hosting-realtime', }, { - id: 'reference_self_hosting_analytics', - path: '/reference/self-hosting-analytics', + id: MenuId.SelfHostingAnalytics, commonSectionsFile: 'common-self-hosting-analytics-sections.json', type: 'reference', + path: '/reference/self-hosting-analytics', }, { - id: 'reference_self_hosting_functions', - path: '/reference/self-hosting-functions', + id: MenuId.SelfHostingFunctions, commonSectionsFile: 'common-self-hosting-functions-sections.json', type: 'reference', + path: '/reference/self-hosting-functions', }, ] -function getMenuById(id: string) { - return menus.find((menu) => menu.id === id) -} - -function getMenuByUrl(basePath: string, url: string) { - // If multiple matches, choose the menu with the longest path - const [menu] = menus - .filter(({ path }) => url.startsWith(`${basePath}${path}`)) - .sort((a, b) => b.path.length - a.path.length) - - return menu +function getMenuById(id: MenuId) { + return menus.find((menu) => menu.id === id) ?? menus.find((menu) => menu.id === MenuId.Home) } function getMenuElement(menu: Menu) { @@ -257,29 +260,12 @@ function getMenuElement(menu: Menu) { } } -const NavigationMenu = () => { - const router = useRouter() - - useEffect(() => { - function handleRouteChange(url: string) { - const menu = getMenuByUrl(router.basePath, url) - if (menu) { - menuState.setMenuLevelId(menu.id) - } - } - - handleRouteChange(router.basePath + router.asPath) - // Listen for page changes after a navigation or when the query changes - router.events.on('routeChangeComplete', handleRouteChange) - return () => { - router.events.off('routeChangeComplete', handleRouteChange) - } - }, [router.asPath, router.basePath, router.events]) - - const level = useMenuLevelId() +const NavigationMenu = ({ menuId }: { menuId: MenuId }) => { + const level = menuId const menu = getMenuById(level) return getMenuElement(menu) } +export { MenuId } export default memo(NavigationMenu) diff --git a/apps/docs/components/reference/RefSectionHandler.tsx b/apps/docs/components/reference/RefSectionHandler.tsx index e100cfbcb20..a633824d45f 100644 --- a/apps/docs/components/reference/RefSectionHandler.tsx +++ b/apps/docs/components/reference/RefSectionHandler.tsx @@ -2,6 +2,7 @@ import Head from 'next/head' import { useRouter } from 'next/router' import { useEffect } from 'react' +import { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import RefEducationSection from '~/components/reference/RefEducationSection' import RefFunctionSection from '~/components/reference/RefFunctionSection' @@ -10,6 +11,7 @@ import ApiOperationSection from './ApiOperationSection' import CliCommandSection from './CLICommandSection' import OldVersionAlert from './OldVersionAlert' import { IAPISpec, ICommonSection, IRefStaticDoc, ISpec, TypeSpec } from './Reference.types' +import { MainSkeleton } from '~/layouts/MainSkeleton' interface RefSectionHandlerProps { sections: ICommonSection[] @@ -18,6 +20,7 @@ interface RefSectionHandlerProps { pageProps: { docs: IRefStaticDoc[] } type: 'client-lib' | 'cli' | 'api' isOldVersion?: boolean + menuId: MenuId } const RefSectionHandler = (props: RefSectionHandlerProps) => { @@ -75,53 +78,55 @@ const RefSectionHandler = (props: RefSectionHandlerProps) => { - {props.isOldVersion && } - - {props.sections.map((section, i) => { - const sectionType = section.type - switch (sectionType) { - case 'markdown': - const markdownData = props.pageProps.docs.find((doc) => doc.id === section.id) + + {props.isOldVersion && } + + {props.sections.map((section, i) => { + const sectionType = section.type + switch (sectionType) { + case 'markdown': + const markdownData = props.pageProps.docs.find((doc) => doc.id === section.id) - return ( - - ) - case 'function': - return ( - - ) - case 'cli-command': - return ( - - ) - case 'operation': - return ( - - ) - default: - throw new Error(`Unknown common section type '${sectionType}'`) - } - })} - + return ( + + ) + case 'function': + return ( + + ) + case 'cli-command': + return ( + + ) + case 'operation': + return ( + + ) + default: + throw new Error(`Unknown common section type '${sectionType}'`) + } + })} + + ) } diff --git a/apps/docs/content/guides/auth/social-login/auth-apple.mdx b/apps/docs/content/guides/auth/social-login/auth-apple.mdx index 1bc10b0de90..daa141be5f0 100644 --- a/apps/docs/content/guides/auth/social-login/auth-apple.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-apple.mdx @@ -240,15 +240,16 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app, 1. Have an **App ID** which uniquely identifies the app you are building. You can create a new App ID from the [Identifiers](https://developer.apple.com/account/resources/identifiers/list/bundleId) section in the Apple Developer Console (use the filter menu in the upper right side to see all App IDs). These usually are a reverse domain name string, for example `com.example.app`. Make sure you configure Sign in with Apple for the App ID you created or already have, in the Capabilities list. At this time Supabase Auth does not support Server-to-Server notification endpoints, so you should leave that setting blank. (In the past App IDs were referred to as _bundle IDs._) 2. Register all of the App IDs that will be using your Supabase project in the [Apple provider configuration in the Supabase dashboard](https://supabase.com/dashboard/project/_/auth/providers) under _Authorized Client IDs_. - - - If you're building a native app only, you do not need to configure the OAuth settings. - - - ## Apple sign in on Android, Web, Windows and Linux For platforms that doesn't support native Apple sign in, you can use the `signInWithOAuth()` method to perform the Apple sign in. + + + + Do **NOT** follow the Android or Web setup instructions on [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) package README for these platforms. sign_in_with_apple package is not used for performing Apple sign-in on non-Apple platforms for Supabase. + + + This method of signing in is web based, and will open a browser window to perform the sign in. For non-web platforms, the user is brought back to the app via [deep linking](/docs/guides/auth/native-mobile-deep-linking?platform=flutter). ```dart diff --git a/apps/docs/content/guides/auth/social-login/auth-google.mdx b/apps/docs/content/guides/auth/social-login/auth-google.mdx index 38f6936b717..6279925e6f3 100644 --- a/apps/docs/content/guides/auth/social-login/auth-google.mdx +++ b/apps/docs/content/guides/auth/social-login/auth-google.mdx @@ -16,7 +16,7 @@ There are three general ways to use Sign in with Google, depending on the applic - Sign in on the web or in web-based apps - Using an OAuth flow initiated by Supabase Auth using [Google Identity Authorization with OAuth 2.0 for Web Server Applications](https://developers.google.com/identity/protocols/oauth2/web-server). - Using a [personalized sign-in button](https://developers.google.com/identity/gsi/web/guides/personalized-button) or [One Tap](https://developers.google.com/identity/gsi/web/guides/display-google-one-tap) and [automatic sign-in](https://developers.google.com/identity/gsi/web/guides/automatic-sign-in-sign-out) for users already logged in to their Google account. -- Sign in in native Android apps and Chrome extensions +- Sign in in native Android apps and Chrome extensions. In some cases you're able to use the OAuth flow within web-based native apps such as with React Native, Expo or other similar frameworks. It is best practice to use native Sign in with Google capabilities whenever possible. @@ -232,7 +232,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf - ## Using native sign in with Google in Flutter + ## Using native sign-in with Google on Flutter Native Google sign in with Supabase is done through the [google_sign_in](https://pub.dev/packages/google_sign_in) package for iOS and Android. Follow the instructions on the [google_sign_in](https://pub.dev/packages/google_sign_in) package for plugin specific settings. @@ -301,11 +301,114 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf - Unlike the OAuth flow which requires the use of a web browser, the native Sign in with Google flow on Android uses the [operating system's built-in functionalities](https://developers.google.com/android/reference/com/google/android/gms/auth/api/identity/package-summary) to prompt the user for consent. Note that native sign-in has been rebranded as _One Tap sign-in on Android_ by Google, which you should not confuse with _One Tap sign in for web_, as mentioned below. + ## Using Google sign-in on Android + + The Sign in with Google flow on Android uses the [operating system's built-in functionalities](https://developer.android.com/training/sign-in/credential-manager) to prompt the user for consent. When the user provides consent, Google issues an identity token (commonly abbreviated as ID token) that is then sent to your project's Supabase Auth server. When valid, a new user session is started by issuing an access and refresh token from Supabase Auth. - When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap (or Credential Manager on Android 14+) automatically and on other platforms it uses `auth.signInWith(Google)`. + **Note:** You have to create OAuth client IDs for both a Web and Android application. The Web client ID is the one used in your Android app. + + Add the following dependencies to your app. You can find the latest version of `credentials` [here](https://developer.android.com/jetpack/androidx/releases/credentials) and `googleid` [here](https://developers.google.com/identity/android-credential-manager/releases). + + ```kotlin + implementation("androidx.credentials:credentials:") + implementation ("com.google.android.libraries.identity.googleid:googleid:") + + // optional - needed for credentials support from play services, for devices running + // Android 13 and below. + implementation("androidx.credentials:credentials-play-services-auth:") + ``` + + Add the following proguard rules to your `proguard-rules.pro` file: + + ```proguard + -if class androidx.credentials.CredentialManager + -keep class androidx.credentials.playservices.** { + *; + } + ``` + + Follow the code sample below to implement native Google sign-in with Supabase using Credential Manager in your Android app. + + ```kotlin + @Composable + fun GoogleSignInButton() { + val coroutineScope = rememberCoroutineScope() + val context = LocalContext.current + + val onClick: () -> Unit = { + val credentialManager = CredentialManager.create(context) + + // Generate a nonce and hash it with sha-256 + // Providing a nonce is optional but recommended + val rawNonce = UUID.randomUUID().toString() // Generate a random String. UUID should be sufficient, but can also be any other random string. + val bytes = rawNonce.toString().toByteArray() + val md = MessageDigest.getInstance("SHA-256") + val digest = md.digest(bytes) + val hashedNonce = digest.fold("") { str, it -> str + "%02x".format(it) } // Hashed nonce to be passed to Google sign-in + + + val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder() + .setFilterByAuthorizedAccounts(false) + .setServerClientId("WEB_GOOGLE_CLIENT_ID") + .setNonce(hashedNonce) // Provide the nonce if you have one + .build() + + val request: GetCredentialRequest = GetCredentialRequest.Builder() + .addCredentialOption(googleIdOption) + .build() + + coroutineScope.launch { + try { + val result = credentialManager.getCredential( + request = request, + context = context, + ) + + val googleIdTokenCredential = GoogleIdTokenCredential + .createFrom(result.credential.data) + + val googleIdToken = googleIdTokenCredential.idToken + + supabase.auth.signInWith(IDToken) { + idToken = googleIdToken + provider = Google + nonce = rawNonce + } + + // Handle successful sign-in + } catch (e: GetCredentialException) { + // Handle GetCredentialException thrown by `credentialManager.getCredential()` + } catch (e: GoogleIdTokenParsingException) { + // Handle GoogleIdTokenParsingException thrown by `GoogleIdTokenCredential.createFrom()` + } catch (e: RestException) { + // Handle RestException thrown by Supabase + } catch (e: Exception) { + // Handle unknown exceptions + } + } + } + + Button( + onClick = onClick, + ) { + Text("Sign in with Google") + } + } + ``` + + ### Configuration [#android-configuration] + + 1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. You need two client IDs, a web client ID and Android client ID. When creating a new Android OAuth client ID, use the instructions on screen to provide the SHA-1 certificate fingerprint used to sign your Android app. + 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service. + 3. Finally, add the web client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. + + Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in. + + ## Using Google sign-in with Kotlin Multiplatform + + When using [Compose Multiplatform](https://www.jetbrains.com/lp/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap (or Credential Manager on Android 14+) automatically and on other platforms it uses `auth.signInWith(Google)`. Alternatively, you can have a look at [Google One Tap](https://developers.google.com/identity/one-tap/android/overview), just login with the IdToken after you receive it. **Initialize the Supabase Client** @@ -343,7 +446,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf } ``` - ### Configuration [#kotlin-configuration-native-app] + ### Configuration [#kotlin-multiplatform-configuration] 1. Configure OAuth credentials for your Google Cloud project in the [Credentials](https://console.cloud.google.com/apis/credentials) page of the console. When creating a new OAuth client ID, choose _Android_ or _iOS_ depending on the mobile operating system your app is built for. @@ -353,8 +456,6 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf 2. Configure the [OAuth Consent Screen](https://console.cloud.google.com/apis/credentials/consent). This information is shown to the user when giving consent to your app. In particular, make sure you have set up links to your app's privacy policy and terms of service. 3. Finally, add the client ID from step 1 in the [Google provider on the Supabase Dashboard](https://supabase.com/dashboard/project/_/auth/providers), under _Authorized Client IDs_. - Note that you do not have to configure the OAuth flow in the Supabase Dashboard in order to use native sign in. - diff --git a/apps/docs/content/guides/functions/dart-edge.mdx b/apps/docs/content/guides/functions/dart-edge.mdx index 32f89e469a4..98b45295dfe 100644 --- a/apps/docs/content/guides/functions/dart-edge.mdx +++ b/apps/docs/content/guides/functions/dart-edge.mdx @@ -2,20 +2,16 @@ id: 'functions-dart-edge' title: 'Dart Edge' description: 'Write your functions using Dart.' +tocVideo: '53jhgrDONW4' --- [Dart Edge](https://docs.dartedge.dev/), a project built and maintained by [Invertase](https://invertase.io/), enables Dart code to be run on different edge environments. This guide will walk you through how you can start using Dart Edge to write your backend code using Dart and run it on Supabase. -> ⚠️ WARNING: Dart Edge is an experimental project - it's probably not ready for production usage unless you're okay with living on the 'edge'. Open issues on [Dart Edge repo](https://github.com/invertase/dart_edge) if you encounter any issues. + -
- -
+Dart Edge is an experimental project - it's probably not ready for production usage unless you're okay with living on the 'edge'. Open issues on [Dart Edge repo](https://github.com/invertase/dart_edge) if you encounter any issues. + +
## Pre requirements @@ -122,10 +118,29 @@ void main() { ### Run locally +Create a `edge.yaml` file if you don't have one yet in the root of your project. Then, add the following configuration to specify the path to your Supabase project. + +```yaml +supabase: + project_path: './supabase' +``` + +Also, currently Dart Edge doesn't work with the latest versions of Flutter. If you do have the latest version of Flutter on your machine, you can use [FVM](https://fvm.app/) to use an older version of Flutter for your Dart Edge project. + +```bash +fvm use 3.7.12 -f +``` + +You can read more about how to configure FVM (Flutter +Version Management) for your project in the [FVM documentation](https://fvm.app/documentation/getting-started). + Run the following command to compile your Dart code. This command will start a watcher that will recompile anytime you make changes to your code. ```bash edge build supabase_functions --dev + +# Or with FVM +fvm dart pub run edge build supabase_functions --dev ``` With docker running, start your local Supabase with the following command diff --git a/apps/docs/content/guides/platform/ipv4-address.mdx b/apps/docs/content/guides/platform/ipv4-address.mdx index 2a15dc1332e..5d5c1038df8 100644 --- a/apps/docs/content/guides/platform/ipv4-address.mdx +++ b/apps/docs/content/guides/platform/ipv4-address.mdx @@ -1,11 +1,9 @@ --- -title: 'IPv4 Address' +title: 'Dedicated IPv4 Address' description: 'Attach an IPv4 address to your database' subtitle: 'Attach an IPv4 address to your database' --- -From 26th January 2024, we will begin migrating projects to IPv6. To read more about why we are doing this migration, read [this blog post](https://supabase.com/blog/ipv6) and [this discussion](https://github.com/orgs/supabase/discussions/17817) for how to prepare for the migration. As a summary, AWS will [start charging for IPv4 addresses](https://aws.amazon.com/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/). - The add-on is $4 per month - we're passing on the AWS costs to you. The add-on is available for all paid plans. ## When is this add-on required? @@ -25,7 +23,7 @@ When you purchase this add-on, your project won't be migrated to IPv6. PGBouncer ## Enabling the add-on -You can now enable the IPv4 add-on in your project [add-on settings](https://supabase.com/dashboard/project/_/settings/addons) page. +You can enable the IPv4 add-on in your project [add-on settings](https://supabase.com/dashboard/project/_/settings/addons) page. Note that there might be some downtime when toggling the add-on since some DNS clients might have @@ -33,6 +31,10 @@ You can now enable the IPv4 add-on in your project [add-on settings](https://sup Generally, this should be less than a minute. -IPv4 add-on page +## Caveats -IPv4 add-on page +For regular use-cases like a restart or compute size changes, the IPv4 address remains unchanged. +However, there are cases where maintaining the same IPv4 address isn't guaranteed. These include: + +- If the project gets paused and unpaused +- If the add-on gets removed and added again diff --git a/apps/docs/content/guides/platform/maturity-model.mdx b/apps/docs/content/guides/platform/maturity-model.mdx index 1a4f414063e..8bcad22377b 100644 --- a/apps/docs/content/guides/platform/maturity-model.mdx +++ b/apps/docs/content/guides/platform/maturity-model.mdx @@ -22,12 +22,14 @@ Resources: ## In production -Once your application is live, you should never change your database using the Dashboard. Supabase provides various [access levels](https://supabase.com/docs/guides/platform/access-control) for the Dashboard that can help enforce this. Some other important things to consider at this point include: +Once your application is live, you should never change your database using the Dashboard - everything should be done with [Migrations](/docs/guides/cli/managing-environments#create-a-new-migration). Some other important things to consider at this point include: -- Read the [Production Checklist](/docs/guides/platform/going-into-prod). -- Make your team aware of the [Shared Responsibilities](/docs/guides/platform/shared-responsibility-model) between your organization and Supabase. -- Design a [safe workflow](https://supabase.com/docs/guides/platform/shared-responsibility-model#you-decide-your-own-workflow) for managing your database. We strongly recommend running [multiple environments](/docs/guides/cli/managing-environments) as part of your development workflow (`local` -> `staging` -> `prod`). +- The Dashboard has various [access levels](/docs/guides/platform/access-control) that can prevent changes being made via the UI. +- Design a [safe workflow](/docs/guides/platform/shared-responsibility-model#you-decide-your-own-workflow) for managing your database. We strongly recommend running [multiple environments](/docs/guides/cli/managing-environments) as part of your development workflow (`local` -> `staging` -> `prod`). +- Do not share any production passwords with your team, _especially_ your `postgres` password. All changes should be made via version-controlled migrations which run via a bastion host or a CI platform (like [GitHub Actions](/docs/guides/cli/managing-environments#configure-github-actions). If you use GitHub Actions, use [approval workflows](https://docs.github.com/en/actions/managing-workflow-runs/reviewing-deployments) to prevent any migrations being run accidentally. +- Restrict production access to your database using [Network Restrictions](/docs/guides/platform/network-restrictions). - As your database to grows, we strongly recommend moving to [Point-in-Time Recovery](/docs/guides/platform/backups#point-in-time-recovery). This is safer and has less impact on your database performance during maintenance windows. +- Read the [Production Checklist](/docs/guides/platform/going-into-prod) and familiarize your team with the [Shared Responsibilities](/docs/guides/platform/shared-responsibility-model) between your organization and Supabase. Resources: diff --git a/apps/docs/content/guides/platform/read-replicas.mdx b/apps/docs/content/guides/platform/read-replicas.mdx index d8ef2b741a5..29f048a7a1b 100644 --- a/apps/docs/content/guides/platform/read-replicas.mdx +++ b/apps/docs/content/guides/platform/read-replicas.mdx @@ -135,16 +135,22 @@ All settings made through the dashboard will be propagated across all databases A project needs to satisfy these requirements to spin up a read replica: 1. Running on AWS. - - Support for projects on Fly is in progress. -2. Running on at least a small compute. -3. PostgreSQL 15. - - For projects running on older versions of PostgreSQL, you can initiate self-serve upgrades with pg_upgrade. For details, see the [upgrade guide](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade). + - Support for projects on Fly.io will be added at a later date. +1. Running on at least a [Small compute add-on](/docs/guides/platform/compute-add-ons). +1. Using [physical backups](/docs/guides/platform/backups#point-in-time-recovery) + - Physical backups are automatically enabled if using [PITR](/docs/guides/platform/backups#point-in-time-recovery) +1. Running on Postgres 15. + - For projects running on older versions of PostgreSQL, you will need to [upgrade to the latest platform version](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade). ### Read replicas need to be brought down before starting the following procedures The following procedures require all read replicas for a project to be brought down before they can be started: -1. Upgrading a project via pg_upgrade. -2. Performing data restoration. +1. [Upgrading a project](/docs/guides/platform/migrating-and-upgrading-projects#pgupgrade) +1. Performing [data restoration](/docs/guides/platform/backups#pitr-restoration-process). -Changes made to the primary database during these operations heavily disrupt replication and potentially leave active read replicas in unstable states. Once these operations complete, read replicas can be spun back up. +Once these operations complete, read replicas can be spun back up. + +### Compute Add-ons + +In order to avoid replicas being unable to keep up with activity on the primaries they're following, Read Replicas are currently placed on the same Compute Add-on as the primary they're following. diff --git a/apps/docs/hooks/useMenuState.ts b/apps/docs/hooks/useMenuState.ts index 52977307542..9cbcba155e7 100644 --- a/apps/docs/hooks/useMenuState.ts +++ b/apps/docs/hooks/useMenuState.ts @@ -7,11 +7,6 @@ export const menuState = proxy({ setMenuActiveRefId: (value) => { menuState.menuActiveRefId = value }, - menuLevelId: 'home', - setMenuLevelId: (value) => { - menuState.menuMobileOpen = false - menuState.menuLevelId = value - }, menuMobileOpen: false, setMenuMobileOpen: (value) => { menuState.menuMobileOpen = value @@ -21,9 +16,6 @@ export const menuState = proxy({ export const useMenuActiveRefId = () => { return useSnapshot(menuState).menuActiveRefId } -export const useMenuLevelId = () => { - return useSnapshot(menuState).menuLevelId -} export const useMenuMobileOpen = () => { return useSnapshot(menuState).menuMobileOpen diff --git a/apps/docs/internals/generate-sitemap.mjs b/apps/docs/internals/generate-sitemap.mjs index c82e2709deb..7d099056df7 100644 --- a/apps/docs/internals/generate-sitemap.mjs +++ b/apps/docs/internals/generate-sitemap.mjs @@ -45,7 +45,6 @@ async function generate() { ${`https://supabase.com/docs/${path}`} weekly - 0.5 ` }) diff --git a/apps/docs/layouts/DefaultGuideLayout.tsx b/apps/docs/layouts/DefaultGuideLayout.tsx index 9bda33bb1df..08a632fbc9f 100644 --- a/apps/docs/layouts/DefaultGuideLayout.tsx +++ b/apps/docs/layouts/DefaultGuideLayout.tsx @@ -1,5 +1,6 @@ import { FC } from 'react' import { FooterHelpCalloutType } from '~/components/FooterHelpCallout' +import { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import GuideLayout from './guides' interface Props { @@ -16,6 +17,7 @@ interface Props { hideToc?: boolean currentPage?: string editLink?: string + menuId: MenuId } const Layout: FC = (props) => { diff --git a/apps/docs/layouts/DefaultLayout.tsx b/apps/docs/layouts/DefaultLayout.tsx index b672c43f4a6..6bfc53a2564 100644 --- a/apps/docs/layouts/DefaultLayout.tsx +++ b/apps/docs/layouts/DefaultLayout.tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/router' import { FC, PropsWithChildren } from 'react' import components from '~/components' import TableOfContents from '~/components/TableOfContents' +import { MainSkeleton } from './MainSkeleton' interface Props { meta: { diff --git a/apps/docs/layouts/HomeLayout.tsx b/apps/docs/layouts/HomeLayout.tsx index 30d8627a622..9f5e7d46320 100644 --- a/apps/docs/layouts/HomeLayout.tsx +++ b/apps/docs/layouts/HomeLayout.tsx @@ -4,9 +4,11 @@ import Head from 'next/head' import { useRouter } from 'next/router' import { FC } from 'react' import components from '~/components' +import HomePageCover from '~/components/HomePageCover' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import TableOfContents from '~/components/TableOfContents' -import HomePageCover from '../components/HomePageCover' import { LayoutMainContent } from './DefaultLayout' +import { MainSkeleton } from './MainSkeleton' interface Props { meta: { @@ -61,26 +63,28 @@ const HomeLayout: FC = (props: Props) => { }, }} /> -
- - -
-
- {props.children} + +
+ + +
+
+ {props.children} +
-
- {hasTableOfContents && !props.meta?.hide_table_of_contents && ( - - )} - -
+ {hasTableOfContents && !props.meta?.hide_table_of_contents && ( + + )} + + + ) } diff --git a/apps/docs/layouts/MainSkeleton.tsx b/apps/docs/layouts/MainSkeleton.tsx new file mode 100644 index 00000000000..bf2b77aef4d --- /dev/null +++ b/apps/docs/layouts/MainSkeleton.tsx @@ -0,0 +1,376 @@ +import { useTheme } from 'next-themes' +import Image from 'next/legacy/image' +import Link from 'next/link' +import NavigationMenu, { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' +import TopNavBar from '~/components/Navigation/NavigationMenu/TopNavBar' + +import { PropsWithChildren, memo, useEffect } from 'react' +import Footer from '~/components/Navigation/Footer' +import { menuState, useMenuMobileOpen } from '~/hooks/useMenuState' + +const levelsData = { + home: { + icon: '/docs/img/icons/menu/home', + name: 'Home', + }, + gettingstarted: { + icon: '/docs/img/icons/menu/getting-started', + name: 'Getting Started', + }, + database: { + icon: '/docs/img/icons/menu/database', + name: 'Database', + }, + api: { + icon: '/docs/img/icons/menu/rest', + name: 'REST API', + }, + graphql: { + icon: '/docs/img/icons/menu/graphql', + name: 'GraphQL', + }, + auth: { + icon: '/docs/img/icons/menu/auth', + name: 'Auth', + }, + functions: { + icon: '/docs/img/icons/menu/functions', + name: 'Edge Functions', + }, + realtime: { + icon: '/docs/img/icons/menu/realtime', + name: 'Realtime', + }, + analytics: { + icon: '/docs/img/icons/menu/analytics', + name: 'Analytics', + }, + storage: { + icon: '/docs/img/icons/menu/storage', + name: 'Storage', + }, + ai: { + icon: '/docs/img/icons/menu/ai', + name: 'AI & Vectors', + }, + supabase_cli: { + icon: '/docs/img/icons/menu/reference-cli', + name: 'Supabase CLI', + }, + platform: { + icon: '/docs/img/icons/menu/platform', + name: 'Platform', + }, + resources: { + icon: '/docs/img/icons/menu/resources', + name: 'Resources', + }, + self_hosting: { + icon: '/docs/img/icons/menu/self-hosting', + name: 'Self-Hosting', + }, + integrations: { + icon: '/docs/img/icons/menu/integrations', + name: 'Integrations', + }, + reference_javascript_v1: { + icon: '/docs/img/icons/menu/reference-javascript', + name: 'Javascript Reference v1.0', + }, + reference_javascript_v2: { + icon: '/docs/img/icons/menu/reference-javascript', + name: 'Javascript Reference v2.0', + }, + reference_dart_v1: { + icon: '/docs/img/icons/menu/reference-dart', + name: 'Dart Reference v1.0', + }, + reference_dart_v2: { + icon: '/docs/img/icons/menu/reference-dart', + name: 'Dart Reference v2.0', + }, + reference_csharp_v0: { + icon: '/docs/img/icons/menu/reference-csharp', + name: 'C# Reference v0.0', + }, + reference_python_v2: { + icon: '/docs/img/icons/menu/reference-python', + name: 'Python Reference v2.0', + }, + reference_swift_v1: { + icon: '/docs/img/icons/menu/reference-swift', + name: 'Swift Reference v1.0', + }, + reference_swift_v2: { + icon: '/docs/img/icons/menu/reference-swift', + name: 'Swift Reference v2.0', + }, + reference_kotlin_v1: { + icon: '/docs/img/icons/menu/reference-kotlin', + name: 'Kotlin Reference v1.0', + }, + reference_kotlin_v2: { + icon: '/docs/img/icons/menu/reference-kotlin', + name: 'Kotlin Reference v2.0', + }, + reference_cli: { + icon: '/docs/img/icons/menu/reference-cli', + name: 'CLI Reference', + }, + reference_api: { + icon: '/docs/img/icons/menu/reference-api', + name: 'Management API Reference', + }, + reference_self_hosting_auth: { + icon: '/docs/img/icons/menu/reference-auth', + name: 'Auth Server Reference', + }, + reference_self_hosting_storage: { + icon: '/docs/img/icons/menu/reference-storage', + name: 'Storage Server Reference', + }, + reference_self_hosting_realtime: { + icon: '/docs/img/icons/menu/reference-realtime', + name: 'Realtime Server Reference', + }, + reference_self_hosting_analytics: { + icon: '/docs/img/icons/menu/reference-analytics', + name: 'Analytics Server Reference', + }, + reference_self_hosting_functions: { + icon: '/docs/img/icons/menu/reference-functions', + name: 'Functions Server Reference', + }, +} + +const MobileHeader = memo(function MobileHeader({ menuId }: { menuId: MenuId }) { + const mobileMenuOpen = useMenuMobileOpen() + const menuLevel = menuId + + return ( +
+ +
+ +
+ + {mobileMenuOpen + ? 'Close' + : menuLevel + ? levelsData[menuLevel].name + : levelsData['home'].name} + +
+ ) +}) + +const MobileMenuBackdrop = memo(function MobileMenuBackdrop() { + const mobileMenuOpen = useMenuMobileOpen() + + useEffect(() => { + window.addEventListener('resize', (e: UIEvent) => { + const w = e.target as Window + if (mobileMenuOpen && w.innerWidth >= 1024) { + menuState.setMenuMobileOpen(!mobileMenuOpen) + } + }) + return () => { + window.removeEventListener('resize', () => {}) + } + }, [mobileMenuOpen]) + + return ( +
menuState.setMenuMobileOpen(!mobileMenuOpen)} + >
+ ) +}) + +const HeaderLogo = memo(function HeaderLogo() { + const { resolvedTheme } = useTheme() + return ( + + Supabase Logo + DOCS + + ) +}) + +const Container = memo(function Container(props: PropsWithChildren) { + const mobileMenuOpen = useMenuMobileOpen() + + return ( +
+
{props.children}
+
+ ) +}) + +const NavContainer = memo(function NavContainer({ menuId }: { menuId: MenuId }) { + const mobileMenuOpen = useMenuMobileOpen() + + return ( + + ) +}) + +function MainSkeleton({ children, menuId }: PropsWithChildren<{ menuId: MenuId }>) { + return ( +
+ + +
+ +
+
+
+ +
+
+
+ {children} +
+
+ +
+
+ ) +} + +export { MainSkeleton } diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index ed1447aa80c..5754ddff924 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -1,349 +1,5 @@ -import { useTheme } from 'next-themes' -import Image from 'next/legacy/image' -import Link from 'next/link' -import NavigationMenu from '~/components/Navigation/NavigationMenu/NavigationMenu' -import TopNavBar from '~/components/Navigation/NavigationMenu/TopNavBar' - import Head from 'next/head' -import { PropsWithChildren, memo, useEffect } from 'react' -import Footer from '~/components/Navigation/Footer' -import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState' - -const levelsData = { - home: { - icon: '/docs/img/icons/menu/home', - name: 'Home', - }, - gettingstarted: { - icon: '/docs/img/icons/menu/getting-started', - name: 'Getting Started', - }, - database: { - icon: '/docs/img/icons/menu/database', - name: 'Database', - }, - api: { - icon: '/docs/img/icons/menu/rest', - name: 'REST API', - }, - graphql: { - icon: '/docs/img/icons/menu/graphql', - name: 'GraphQL', - }, - auth: { - icon: '/docs/img/icons/menu/auth', - name: 'Auth', - }, - functions: { - icon: '/docs/img/icons/menu/functions', - name: 'Edge Functions', - }, - realtime: { - icon: '/docs/img/icons/menu/realtime', - name: 'Realtime', - }, - analytics: { - icon: '/docs/img/icons/menu/analytics', - name: 'Analytics', - }, - storage: { - icon: '/docs/img/icons/menu/storage', - name: 'Storage', - }, - ai: { - icon: '/docs/img/icons/menu/ai', - name: 'AI & Vectors', - }, - supabase_cli: { - icon: '/docs/img/icons/menu/reference-cli', - name: 'Supabase CLI', - }, - platform: { - icon: '/docs/img/icons/menu/platform', - name: 'Platform', - }, - resources: { - icon: '/docs/img/icons/menu/resources', - name: 'Resources', - }, - self_hosting: { - icon: '/docs/img/icons/menu/self-hosting', - name: 'Self-Hosting', - }, - integrations: { - icon: '/docs/img/icons/menu/integrations', - name: 'Integrations', - }, - reference_javascript_v1: { - icon: '/docs/img/icons/menu/reference-javascript', - name: 'Javascript Reference v1.0', - }, - reference_javascript_v2: { - icon: '/docs/img/icons/menu/reference-javascript', - name: 'Javascript Reference v2.0', - }, - reference_dart_v1: { - icon: '/docs/img/icons/menu/reference-dart', - name: 'Dart Reference v1.0', - }, - reference_dart_v2: { - icon: '/docs/img/icons/menu/reference-dart', - name: 'Dart Reference v2.0', - }, - reference_csharp_v0: { - icon: '/docs/img/icons/menu/reference-csharp', - name: 'C# Reference v0.0', - }, - reference_python_v2: { - icon: '/docs/img/icons/menu/reference-python', - name: 'Python Reference v2.0', - }, - reference_swift_v1: { - icon: '/docs/img/icons/menu/reference-swift', - name: 'Swift Reference v1.0', - }, - reference_swift_v2: { - icon: '/docs/img/icons/menu/reference-swift', - name: 'Swift Reference v2.0', - }, - reference_kotlin_v1: { - icon: '/docs/img/icons/menu/reference-kotlin', - name: 'Kotlin Reference v1.0', - }, - reference_kotlin_v2: { - icon: '/docs/img/icons/menu/reference-kotlin', - name: 'Kotlin Reference v2.0', - }, - reference_cli: { - icon: '/docs/img/icons/menu/reference-cli', - name: 'CLI Reference', - }, - reference_api: { - icon: '/docs/img/icons/menu/reference-api', - name: 'Management API Reference', - }, - reference_self_hosting_auth: { - icon: '/docs/img/icons/menu/reference-auth', - name: 'Auth Server Reference', - }, - reference_self_hosting_storage: { - icon: '/docs/img/icons/menu/reference-storage', - name: 'Storage Server Reference', - }, - reference_self_hosting_realtime: { - icon: '/docs/img/icons/menu/reference-realtime', - name: 'Realtime Server Reference', - }, - reference_self_hosting_analytics: { - icon: '/docs/img/icons/menu/reference-analytics', - name: 'Analytics Server Reference', - }, - reference_self_hosting_functions: { - icon: '/docs/img/icons/menu/reference-functions', - name: 'Functions Server Reference', - }, -} - -const MobileHeader = memo(function MobileHeader() { - const mobileMenuOpen = useMenuMobileOpen() - const menuLevel = useMenuLevelId() - - return ( -
- -
- -
- - {mobileMenuOpen - ? 'Close' - : menuLevel - ? levelsData[menuLevel].name - : levelsData['home'].name} - -
- ) -}) - -const MobileMenuBackdrop = memo(function MobileMenuBackdrop() { - const mobileMenuOpen = useMenuMobileOpen() - - useEffect(() => { - window.addEventListener('resize', (e: UIEvent) => { - const w = e.target as Window - if (mobileMenuOpen && w.innerWidth >= 1024) { - menuState.setMenuMobileOpen(!mobileMenuOpen) - } - }) - return () => { - window.removeEventListener('resize', () => {}) - } - }, [mobileMenuOpen]) - - return ( -
menuState.setMenuMobileOpen(!mobileMenuOpen)} - >
- ) -}) - -const HeaderLogo = memo(function HeaderLogo() { - const { resolvedTheme } = useTheme() - return ( - - Supabase Logo - DOCS - - ) -}) - -const Container = memo(function Container(props: PropsWithChildren) { - const mobileMenuOpen = useMenuMobileOpen() - - return ( -
-
{props.children}
-
- ) -}) - -const NavContainer = memo(function NavContainer() { - const mobileMenuOpen = useMenuMobileOpen() - - return ( - - ) -}) +import { PropsWithChildren } from 'react' const SiteLayout = ({ children }: PropsWithChildren<{}>) => { return ( @@ -351,32 +7,7 @@ const SiteLayout = ({ children }: PropsWithChildren<{}>) => { Supabase Docs -
-
- - -
- -
-
-
- -
-
-
- {children} -
-
- -
-
-
+
{children}
) } diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 105e250dd5b..34768852a2e 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -10,8 +10,10 @@ import components from '~/components' import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils' import { FooterHelpCalloutType } from '~/components/FooterHelpCallout' import GuidesTableOfContents from '~/components/GuidesTableOfContents' +import { type MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import useHash from '~/hooks/useHash' import { LayoutMainContent } from '../DefaultLayout' +import { MainSkeleton } from '../MainSkeleton' interface Props { meta: { @@ -30,6 +32,7 @@ interface Props { toc?: any currentPage?: string hideToc?: boolean + menuId: MenuId } const Layout: FC = (props) => { @@ -42,6 +45,8 @@ const Layout: FC = (props) => { const { asPath } = useRouter() const router = useRouter() + const menuId = props.menuId + const EDIT_BUTTON_EXCLUDE_LIST = ['/404'] useEffect(() => { @@ -108,77 +113,79 @@ const Layout: FC = (props) => { }, }} /> - -
-
- {props.meta?.breadcrumb && ( -

{props.meta.breadcrumb}

- )} -
-

{props.meta.title}

- {props.meta?.subtitle && ( -

{props.meta.subtitle}

- )} -
- {props.children} - - {EDIT_BUTTON_EXCLUDE_LIST.includes(router.route) ? ( - <> - ) : ( - - )} -
-
- {!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && ( + + +
- + {!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && ( + + )} +
+
+
) } diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 6e5f75eec60..08e4943e944 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -68,6 +68,7 @@ const nextConfig = { 'mdx-mermaid', 'dayjs', 'shared-data', + 'icons', ], async headers() { return [ diff --git a/apps/docs/package.json b/apps/docs/package.json index b9132607c5d..b934a33525a 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -68,6 +68,7 @@ "remark-gfm": "^3.0.1", "shared-data": "*", "ui": "*", + "icons": "*", "ui-patterns": "*", "unist-builder": "^3.0.1", "unist-util-filter": "^4.0.1", @@ -90,7 +91,7 @@ "jest-environment-jsdom": "^29.7.0", "npm-run-all": "^4.1.5", "openapi-types": "^12.0.2", - "openapi-typescript": "^6.7.2", + "openapi-typescript": "^6.7.4", "tsconfig": "*", "tsx": "^4.6.2", "typescript": "^5.2.2" diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index b1c048b72a4..c75ece1b38b 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -14,7 +14,6 @@ import { CommandMenuProvider, PortalToast, TabsProvider } from 'ui' import { useConsent } from 'ui-patterns/ConsentToast' import Favicons from '~/components/Favicons' -import { IPv4DeprecationBanner } from '~/components/IPv4DeprecationBanner' import SiteLayout from '~/layouts/SiteLayout' import { IS_PLATFORM, IS_PREVIEW } from '~/lib/constants' import { unauthedAllowedPost } from '~/lib/fetch/fetchWrappers' @@ -223,7 +222,6 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
- diff --git a/apps/docs/pages/guides/ai/[[...slug]].tsx b/apps/docs/pages/guides/ai/[[...slug]].tsx index af8cc713906..a19f8a02e54 100644 --- a/apps/docs/pages/guides/ai/[[...slug]].tsx +++ b/apps/docs/pages/guides/ai/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function AiGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/ai/python/[slug].tsx b/apps/docs/pages/guides/ai/python/[slug].tsx index fcbd53160c0..952e4bfa099 100644 --- a/apps/docs/pages/guides/ai/python/[slug].tsx +++ b/apps/docs/pages/guides/ai/python/[slug].tsx @@ -11,6 +11,7 @@ import Layout from '~/layouts/DefaultGuideLayout' import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform' import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition' import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' // We fetch these docs at build time from an external repo const org = 'supabase' @@ -61,7 +62,7 @@ interface PythonClientDocsProps { export default function PythonClientDocs({ source, meta }: PythonClientDocsProps) { return ( - + ) diff --git a/apps/docs/pages/guides/api/[[...slug]].tsx b/apps/docs/pages/guides/api/[[...slug]].tsx index 94951837d6d..d46e03fd7cc 100644 --- a/apps/docs/pages/guides/api/[[...slug]].tsx +++ b/apps/docs/pages/guides/api/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function ApiGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/auth/[[...slug]].tsx b/apps/docs/pages/guides/auth/[[...slug]].tsx index 777f65e6ddd..9e645a917ea 100644 --- a/apps/docs/pages/guides/auth/[[...slug]].tsx +++ b/apps/docs/pages/guides/auth/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function AuthGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/cli/[[...slug]].tsx b/apps/docs/pages/guides/cli/[[...slug]].tsx index 3e690745906..2dd168f672d 100644 --- a/apps/docs/pages/guides/cli/[[...slug]].tsx +++ b/apps/docs/pages/guides/cli/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function CliGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/cli/config.tsx b/apps/docs/pages/guides/cli/config.tsx index 3830af444f1..193f25fca66 100644 --- a/apps/docs/pages/guides/cli/config.tsx +++ b/apps/docs/pages/guides/cli/config.tsx @@ -5,6 +5,8 @@ import GuidesTableOfContents from '~/components/GuidesTableOfContents' import { Heading } from '~/components/CustomHTMLElements' import Head from 'next/head' import { CodeBlock } from 'ui' +import { MainSkeleton } from '~/layouts/MainSkeleton' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' // Parameters are grouped on the page by tag const tocList = [] @@ -29,24 +31,26 @@ export default function Config() { Supabase CLI config -
-
-
-

CLI configuration

-
- {specFile.info.description} -
{content}
+ +
+
+
+

CLI configuration

+
+ {specFile.info.description} +
{content}
+
+
+
+
+ + On this page + + +
-
-
- - On this page - - -
-
-
+ ) } diff --git a/apps/docs/pages/guides/cli/github-action/[slug].tsx b/apps/docs/pages/guides/cli/github-action/[slug].tsx index 049e3f1072d..d06701295e5 100644 --- a/apps/docs/pages/guides/cli/github-action/[slug].tsx +++ b/apps/docs/pages/guides/cli/github-action/[slug].tsx @@ -12,6 +12,7 @@ import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLin import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition' import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle' import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' // We fetch these docs at build time from an external repo const org = 'supabase' @@ -62,7 +63,7 @@ interface ActionDocsProps { export default function ActionDocs({ source, meta }: ActionDocsProps) { return ( - + ) diff --git a/apps/docs/pages/guides/database/[[...slug]].tsx b/apps/docs/pages/guides/database/[[...slug]].tsx index fc7519ad2ba..0ee6416477b 100644 --- a/apps/docs/pages/guides/database/[[...slug]].tsx +++ b/apps/docs/pages/guides/database/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -31,7 +32,7 @@ export default function DatabaseGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/database/extensions/wrappers/[[...slug]].tsx b/apps/docs/pages/guides/database/extensions/wrappers/[[...slug]].tsx index 564d96d51d2..0d34ede0dcb 100644 --- a/apps/docs/pages/guides/database/extensions/wrappers/[[...slug]].tsx +++ b/apps/docs/pages/guides/database/extensions/wrappers/[[...slug]].tsx @@ -10,6 +10,7 @@ import remarkGfm from 'remark-gfm' import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' } import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform' @@ -83,7 +84,7 @@ export default function WrappersDocs({ editLink, }: InferGetStaticPropsType) { return ( - + ) diff --git a/apps/docs/pages/guides/functions/[[...slug]].tsx b/apps/docs/pages/guides/functions/[[...slug]].tsx index 8e41b131d3e..8a8ed0defd2 100644 --- a/apps/docs/pages/guides/functions/[[...slug]].tsx +++ b/apps/docs/pages/guides/functions/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function FunctionsGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/getting-started/[[...slug]].tsx b/apps/docs/pages/guides/getting-started/[[...slug]].tsx index 8791fae3a10..e16c15ac68f 100644 --- a/apps/docs/pages/guides/getting-started/[[...slug]].tsx +++ b/apps/docs/pages/guides/getting-started/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function GettingStartedGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/graphql/[[...slug]].tsx b/apps/docs/pages/guides/graphql/[[...slug]].tsx index 559e10f592b..00eaecfe221 100644 --- a/apps/docs/pages/guides/graphql/[[...slug]].tsx +++ b/apps/docs/pages/guides/graphql/[[...slug]].tsx @@ -5,14 +5,17 @@ import { serialize } from 'next-mdx-remote/serialize' import { isAbsolute, relative } from 'path' import rehypeSlug from 'rehype-slug' import remarkGfm from 'remark-gfm' -import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' } + import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform' import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition' import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle' import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs' +import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' } + // We fetch these docs at build time from an external repo const org = 'supabase' const repo = 'pg_graphql' @@ -114,7 +117,7 @@ interface PGGraphQLDocsProps { export default function PGGraphQLDocs({ source, meta }: PGGraphQLDocsProps) { return ( - + ) diff --git a/apps/docs/pages/guides/platform/[[...slug]].tsx b/apps/docs/pages/guides/platform/[[...slug]].tsx index 06340742556..f20307d7974 100644 --- a/apps/docs/pages/guides/platform/[[...slug]].tsx +++ b/apps/docs/pages/guides/platform/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function PlatformGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/realtime/[[...slug]].tsx b/apps/docs/pages/guides/realtime/[[...slug]].tsx index dddabae2a82..3a90134f5ad 100644 --- a/apps/docs/pages/guides/realtime/[[...slug]].tsx +++ b/apps/docs/pages/guides/realtime/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function RealtimeGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/resources/[[...slug]].tsx b/apps/docs/pages/guides/resources/[[...slug]].tsx index 1fb37bfe708..d73ad930fe5 100644 --- a/apps/docs/pages/guides/resources/[[...slug]].tsx +++ b/apps/docs/pages/guides/resources/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function ResourcesGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/self-hosting/[[...slug]].tsx b/apps/docs/pages/guides/self-hosting/[[...slug]].tsx index fee43e2be5b..c63d1c13b75 100644 --- a/apps/docs/pages/guides/self-hosting/[[...slug]].tsx +++ b/apps/docs/pages/guides/self-hosting/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function SelfHostingGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/guides/self-hosting/analytics/config.tsx b/apps/docs/pages/guides/self-hosting/analytics/config.tsx index 073411ca703..3946fc2c55c 100644 --- a/apps/docs/pages/guides/self-hosting/analytics/config.tsx +++ b/apps/docs/pages/guides/self-hosting/analytics/config.tsx @@ -3,6 +3,7 @@ import { MDXRemote } from 'next-mdx-remote' import { serialize } from 'next-mdx-remote/serialize' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Param from '~/components/Params' import Layout from '~/layouts/DefaultGuideLayout' import { getAnalyticsConfigV0 } from '~/lib/mdx/getConfig' @@ -27,6 +28,7 @@ export const Page = (props: InferGetStaticPropsType) => ( diff --git a/apps/docs/pages/guides/self-hosting/auth/config.tsx b/apps/docs/pages/guides/self-hosting/auth/config.tsx index 50734320dfe..5702f8a5379 100644 --- a/apps/docs/pages/guides/self-hosting/auth/config.tsx +++ b/apps/docs/pages/guides/self-hosting/auth/config.tsx @@ -3,6 +3,7 @@ import { MDXRemote } from 'next-mdx-remote' import { serialize } from 'next-mdx-remote/serialize' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Param from '~/components/Params' import Layout from '~/layouts/DefaultGuideLayout' import { getAuthConfigV1 } from '~/lib/mdx/getConfig' @@ -27,6 +28,7 @@ export const Page = (props: InferGetStaticPropsType) => ( diff --git a/apps/docs/pages/guides/self-hosting/realtime/config.tsx b/apps/docs/pages/guides/self-hosting/realtime/config.tsx index 1dd5086b214..09df809537c 100644 --- a/apps/docs/pages/guides/self-hosting/realtime/config.tsx +++ b/apps/docs/pages/guides/self-hosting/realtime/config.tsx @@ -3,6 +3,7 @@ import { MDXRemote } from 'next-mdx-remote' import { serialize } from 'next-mdx-remote/serialize' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Param from '~/components/Params' import Layout from '~/layouts/DefaultGuideLayout' import { getRealtimeConfigV0 } from '~/lib/mdx/getConfig' @@ -27,6 +28,7 @@ export const Page = (props: InferGetStaticPropsType) => ( diff --git a/apps/docs/pages/guides/self-hosting/storage/config.tsx b/apps/docs/pages/guides/self-hosting/storage/config.tsx index 57a32f7cb96..8a131f674ac 100644 --- a/apps/docs/pages/guides/self-hosting/storage/config.tsx +++ b/apps/docs/pages/guides/self-hosting/storage/config.tsx @@ -3,6 +3,7 @@ import { MDXRemote } from 'next-mdx-remote' import { serialize } from 'next-mdx-remote/serialize' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Param from '~/components/Params' import Layout from '~/layouts/DefaultGuideLayout' import { getStorageConfigV0 } from '~/lib/mdx/getConfig' @@ -27,6 +28,7 @@ export const Page = (props: InferGetStaticPropsType) => ( diff --git a/apps/docs/pages/guides/storage/[[...slug]].tsx b/apps/docs/pages/guides/storage/[[...slug]].tsx index c3ae4c4b3c3..90c4b978d88 100644 --- a/apps/docs/pages/guides/storage/[[...slug]].tsx +++ b/apps/docs/pages/guides/storage/[[...slug]].tsx @@ -2,6 +2,7 @@ import { type GetStaticPaths, type GetStaticProps, type InferGetStaticPropsType import { MDXRemote } from 'next-mdx-remote' import components from '~/components' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import Layout from '~/layouts/DefaultGuideLayout' import { getGuidesStaticPaths, getGuidesStaticProps } from '~/lib/docs' @@ -21,7 +22,7 @@ export default function StorageGuide({ const { hideToc, ...meta } = frontmatter return ( - + ) diff --git a/apps/docs/pages/handbook/contributing.mdx b/apps/docs/pages/handbook/contributing.mdx deleted file mode 100644 index 5365b2fb9f9..00000000000 --- a/apps/docs/pages/handbook/contributing.mdx +++ /dev/null @@ -1,32 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'contributing', - title: 'Contributing', - description: 'Want to help?', -} - -## How to contribute - -Supabase has many [open source repos](https://github.com/orgs/supabase/repositories). Some of the more popular ones are: - -- [Supabase](https://github.com/supabase/supabase) - Our Dashboard, Websites, and example apps -- [Supabase-js](https://github.com/supabase/supabase-js) - A client-side library written in TypeScript -- [Gotrue](https://github.com/supabase/gotrue) - Our auth server, written in Go -- [Realtime](https://github.com/supabase/realtime) - A server for streaming changes from Postgres, written in Elixir -- [browse all](https://github.com/orgs/supabase/repositories) - -Want to contribute? Why not jump into our GitHub repo and: - -- [Sponsor Supabase](https://github.com/sponsors/supabase). -- [Answer Discussions](https://github.com/supabase/supabase/discussions). -- Submit an issue to one of our [repos](https://github.com/orgs/supabase/repositories) -- Report a performance issue or a part of the documentation that you find confusing. -- Create a pull request in one of our [repos](https://github.com/orgs/supabase/repositories) -- [Translate our Readme](https://github.com/supabase/supabase/issues/1341). -- Try our products and give feedback. -- Spread the word if you like what we are doing. - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/handbook/introduction.mdx b/apps/docs/pages/handbook/introduction.mdx deleted file mode 100644 index f6f2fc2c162..00000000000 --- a/apps/docs/pages/handbook/introduction.mdx +++ /dev/null @@ -1,21 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'introduction', - title: 'Supabase Handbook', - description: 'The Supabase public handbook and manifesto', -} - -Supabase is thinking in public. Everything is online, no matter how raw the current state of things. - -This is our public handbook and manifesto. We are completely opensource and this means that you, our users, are also our team members. - ---- - -## Opensource - -Wherever we can we will support existing opensource tools and libraries. - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/reference/analytics/[...slug].tsx b/apps/docs/pages/reference/analytics/[...slug].tsx deleted file mode 100644 index 6ba5e15111c..00000000000 --- a/apps/docs/pages/reference/analytics/[...slug].tsx +++ /dev/null @@ -1,168 +0,0 @@ -import specFile from '~/spec/transforms/analytics_v0_openapi_deparsed.json' assert { type: 'json' } -import { gen_v3, enrichedOperation } from '~/lib/refGenerator/helpers' -import { Tabs, CodeBlock } from 'ui' - -import RefSubLayout from '~/layouts/ref/RefSubLayout' - -export type AcceptedValue = { - id: string - name: string - type: 'string' | 'boolean' | 'object' - description?: string -} - -export type Flag = { - id: string - name: string - description: string - default_value: string - accepted_values: AcceptedValue[] -} - -export type ApiParameter = { - example: string - in: string - name: string - required: boolean - schema: { - type: string - example: string - } -} -// @ts-ignore -const generatedSpec = gen_v3(specFile, 'wat', { apiUrl: 'apiv0' }) - -export default function Config() { - return ( - -
-
-
-

{generatedSpec.info.title}

-

{generatedSpec.info.description}

-
- -
- {generatedSpec.operations.map((operation: any) => ( -
- - -
- - {operation.operation} - {operation.fullPath} - -
- {/* Path Parameters */} - {operation.parameters && - operation.parameters.filter((parameter) => parameter.in === 'path').length > - 0 && ( -
-

Path parameters

-
    - {operation.parameters && - operation.parameters - .filter((parameter: any) => parameter.in === 'path') - .map((parameter: any) => ( -
  • -
    -
    - {parameter.name} -
    - {parameter.required && ( -
    - REQUIRED -
    - )} -
    -
    -

    {parameter.description}

    -
    - {parameter.example && ( -
    - Example: - - {parameter.example} - -
    - )} -
  • - ))} -
-
- )} - - {/* Header Parameters */} - {operation.parameters && - operation.parameters.filter((parameter) => parameter.in === 'header').length > - 0 && ( -
-

Header parameters

-
    - {operation.parameters && - operation.parameters - .filter((parameter: any) => parameter.in === 'header') - .map((parameter: any) => ( -
  • -
    - {parameter.name} - - {parameter.required && 'required'} - -
    -
    - Example: - - {parameter.example} - -
    -
  • - ))} -
-
- )} -
- - {operation.responseList.length > 0 && ( - <> -

Responses

- - {operation.responseList.map((response: any) => ( - -

{response.description}

- {response?.content && response?.content['application/json'] && ( -
- - {JSON.stringify(response.content['application/json'], null, 2)} - -
- )} -
- ))} -
- - )} -
-
-
- ))} -
-
-
-
- ) -} diff --git a/apps/docs/pages/reference/analytics/config.tsx b/apps/docs/pages/reference/analytics/config.tsx deleted file mode 100644 index 4db5c72958f..00000000000 --- a/apps/docs/pages/reference/analytics/config.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import fs from 'fs' -import matter from 'gray-matter' -import components from '~/components/index' -import { MDXRemote } from 'next-mdx-remote' -import { serialize } from 'next-mdx-remote/serialize' - -import specFile from '~/spec/analytics_v0_config.yaml' assert { type: 'yml' } -import { Parameter } from '~/lib/refGenerator/refTypes' - -import ReactMarkdown from 'react-markdown' - -// Parameters are grouped on the page by tag -const TAGS = ['general', 'database'] - -export default function Config(props) { - return ( -
-
-
-

{specFile.info.title} Configuration

- {specFile.info.description} -
-
- {props.docs - .filter((doc) => doc.introPage) - .map((item) => ( - - ))} -
- {TAGS.map((tag) => - specFile.parameters - .filter((param: Parameter) => param.tags[0] === tag) - .map((parameter: Parameter, index) => ( -
- {index === 0 &&

{tag} Settings

} -
-
-

- $ - {parameter.title} -

-
-
-
-

- {parameter.description} -

-
-
- Required: {parameter.required.toString()} -
-
- Default: - - {parameter.default ? parameter.default.toString() : 'None'} - -
-
-
- {parameter.links && - parameter.links.map((link) => ( -
-

See also:

-
  • - {link.name} -
  • -
    - ))} -
    -
    -
    -
    -
    - )) - )} -
    -
    -
    -
    - ) -} -export async function getServerSideProps() { - // an array of ids of the intro sections for this library - const introPages = ['analytics'] - - const pages = [...introPages] - - // Grab custom markdown intro page files - const allMarkdownDocs = await Promise.all( - pages.map(async (x: any, i) => { - const pathName = `docs/ref/analytics/${x}.mdx` - - const markdownExists = fs.existsSync(pathName) - - const fileContents = markdownExists ? fs.readFileSync(pathName, 'utf8') : '' - const { data, content } = matter(fileContents) - - return { - id: x, - title: x, - // ...content, - meta: data, - introPage: introPages.includes(x), - content: content ? await serialize(content || '') : null, - } - }) - ) - - return { - props: { - docs: allMarkdownDocs, - }, - } -} diff --git a/apps/docs/pages/reference/api/[...slug].tsx b/apps/docs/pages/reference/api/[...slug].tsx index 27760ce04d8..d9c72242668 100644 --- a/apps/docs/pages/reference/api/[...slug].tsx +++ b/apps/docs/pages/reference/api/[...slug].tsx @@ -1,18 +1,28 @@ -import apiCommonSections from '~/spec/common-api-sections.json' assert { type: 'json' } -import specFile from '~/spec/transforms/api_v0_openapi_deparsed.json' assert { type: 'json' } +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import RefSectionHandler from '~/components/reference/RefSectionHandler' import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' import { gen_v3 } from '~/lib/refGenerator/helpers' +import apiCommonSections from '~/spec/common-api-sections.json' assert { type: 'json' } +import specFile from '~/spec/transforms/api_v0_openapi_deparsed.json' assert { type: 'json' } + // @ts-ignore const generatedSpec = gen_v3(specFile, 'wat', { apiUrl: 'apiv0' }) const sections = flattenSections(apiCommonSections) const libraryPath = '/api' export default function Config(props) { - return + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/auth/[...slug].tsx b/apps/docs/pages/reference/auth/[...slug].tsx deleted file mode 100644 index 8664fe97acb..00000000000 --- a/apps/docs/pages/reference/auth/[...slug].tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { CodeBlock, Tabs } from 'ui' -import specFile from '~/spec/transforms/auth_v1_openapi_deparsed.json' assert { type: 'json' } -import { gen_v3 } from '~/lib/refGenerator/helpers' - -import RefSubLayout from '~/layouts/ref/RefSubLayout' - -export type AcceptedValue = { - id: string - name: string - type: 'string' | 'boolean' | 'object' - description?: string -} - -export type Flag = { - id: string - name: string - description: string - default_value: string - accepted_values: AcceptedValue[] -} - -export type ApiParameter = { - example: string - in: string - name: string - required: boolean - schema: { - type: string - example: string - } -} -// @ts-ignore -const generatedSpec = gen_v3(specFile, 'wat', { apiUrl: 'apiv0' }) - -export default function Config() { - return ( - -
    -
    -
    -

    {generatedSpec.info.title}

    -

    {generatedSpec.info.description}

    -
    - -
    - {generatedSpec.operations.map((operation: any) => ( -
    - - -
    - - {operation.operation} - {operation.fullPath} - -
    - {/* Path Parameters */} - {operation.parameters && - operation.parameters.filter((parameter) => parameter.in === 'path').length > - 0 && ( -
    -

    Path parameters

    -
      - {operation.parameters && - operation.parameters - .filter((parameter: any) => parameter.in === 'path') - .map((parameter: any) => ( -
    • -
      -
      - {parameter.name} -
      - {parameter.required && ( -
      - REQUIRED -
      - )} -
      -
      -

      {parameter.description}

      -
      - {parameter.example && ( -
      - Example: - - {parameter.example} - -
      - )} -
    • - ))} -
    -
    - )} - - {/* Header Parameters */} - {operation.parameters && - operation.parameters.filter((parameter) => parameter.in === 'header').length > - 0 && ( -
    -

    Header parameters

    -
      - {operation.parameters && - operation.parameters - .filter((parameter: any) => parameter.in === 'header') - .map((parameter: any) => ( -
    • -
      - {parameter.name} - - {parameter.required && 'required'} - -
      -
      - Example: - - {parameter.example} - -
      -
    • - ))} -
    -
    - )} -
    - - {operation.responseList.length > 0 && ( - <> -

    Responses

    - - {operation.responseList.map((response: any) => ( - -

    {response.description}

    - {response?.content && response?.content['application/json'] && ( -
    - - {JSON.stringify(response.content['application/json'], null, 2)} - -
    - )} -
    - ))} -
    - - )} -
    -
    -
    - ))} -
    -
    -
    -
    - ) -} diff --git a/apps/docs/pages/reference/auth/config.tsx b/apps/docs/pages/reference/auth/config.tsx deleted file mode 100644 index d9c1e5eacbf..00000000000 --- a/apps/docs/pages/reference/auth/config.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import specFile from '~/spec/gotrue_v1_config.yaml' assert { type: 'yml' } -import { Parameter } from '~/lib/refGenerator/refTypes' -import ReactMarkdown from 'react-markdown' -import Head from 'next/head' - -// Parameters are grouped on the page by tag -const TAGS = ['general'] - -export default function Config() { - return ( - <> - - Auth config - -
    -
    -
    -

    {specFile.info.title} Configuration

    - {specFile.info.description} -
    - {TAGS.map((tag) => - specFile.parameters - .filter((param: Parameter) => param.tags[0] === tag) - .map((parameter: Parameter, index) => ( -
    - {index === 0 &&

    {tag}

    } -
    -
    -

    - $ - {parameter.title} -

    -
    -
    -
    -

    - {parameter.description} -

    -
    -
    - Required: {parameter.required.toString()} -
    -
    - Default: - - {parameter.default ? parameter.default.toString() : 'None'} - -
    -
    -
    - {parameter.links && - parameter.links.map((link) => ( -
    -

    See also:

    -
  • - {link.name} -
  • -
    - ))} -
    -
    -
    -
    -
    - )) - )} -
    -
    -
    -
    - - ) -} diff --git a/apps/docs/pages/reference/cli/[...slug].tsx b/apps/docs/pages/reference/cli/[...slug].tsx index 75b9088f7fa..779e04540b6 100644 --- a/apps/docs/pages/reference/cli/[...slug].tsx +++ b/apps/docs/pages/reference/cli/[...slug].tsx @@ -1,17 +1,25 @@ -import spec from '~/spec/cli_v1_commands.yaml' assert { type: 'yml' } - -import { flattenSections } from '~/lib/helpers' - -import cliCommonSections from '~/spec/common-cli-sections.json' assert { type: 'json' } +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import spec from '~/spec/cli_v1_commands.yaml' assert { type: 'yml' } +import cliCommonSections from '~/spec/common-cli-sections.json' assert { type: 'json' } + const sections = flattenSections(cliCommonSections) const libraryPath = '/cli' export default function CliRef(props) { - return + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/csharp/[...slug].tsx b/apps/docs/pages/reference/csharp/[...slug].tsx index 8145e38f7c9..77dba701704 100644 --- a/apps/docs/pages/reference/csharp/[...slug].tsx +++ b/apps/docs/pages/reference/csharp/[...slug].tsx @@ -4,12 +4,21 @@ import RefSectionHandler from '~/components/reference/RefSectionHandler' import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' const sections = flattenSections(clientLibsCommonSections) const libraryPath = '/csharp' export default function CSharpReference(props) { - return + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/csharp/crawlers/[...slug].tsx b/apps/docs/pages/reference/csharp/crawlers/[...slug].tsx index 0d515778deb..aa454e5d162 100644 --- a/apps/docs/pages/reference/csharp/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/csharp/crawlers/[...slug].tsx @@ -7,6 +7,7 @@ import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' import { useRouter } from 'next/router' import RefSEO from '~/components/reference/RefSEO' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' const sections = flattenSections(clientLibsCommonSections) const libraryPath = '/csharp' @@ -25,6 +26,7 @@ export default function CSharpReference(props) { + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/csharp/v0/crawlers/[...slug].tsx b/apps/docs/pages/reference/csharp/v0/crawlers/[...slug].tsx index 5e20575d173..101c289bddb 100644 --- a/apps/docs/pages/reference/csharp/v0/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/csharp/v0/crawlers/[...slug].tsx @@ -7,6 +7,7 @@ import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' import { useRouter } from 'next/router' import RefSEO from '~/components/reference/RefSEO' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' const sections = flattenSections(clientLibsCommonSections) const libraryPath = '/csharp/v0' @@ -24,6 +25,7 @@ export default function JSReference(props) { + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/dart/crawlers/[...slug].tsx b/apps/docs/pages/reference/dart/crawlers/[...slug].tsx index faace96a0ba..b18c2eeff9b 100644 --- a/apps/docs/pages/reference/dart/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/dart/crawlers/[...slug].tsx @@ -7,6 +7,7 @@ import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' import { useRouter } from 'next/router' import RefSEO from '~/components/reference/RefSEO' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' const sections = flattenSections(clientLibsCommonSections) const libraryPath = '/dart' @@ -25,6 +26,7 @@ export default function DartReference(props) { + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/kotlin/crawlers/[...slug].tsx b/apps/docs/pages/reference/kotlin/crawlers/[...slug].tsx index 2a0d1cadc29..2f3885d524b 100644 --- a/apps/docs/pages/reference/kotlin/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/kotlin/crawlers/[...slug].tsx @@ -7,6 +7,7 @@ import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' import { useRouter } from 'next/router' import RefSEO from '~/components/reference/RefSEO' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' const sections = flattenSections(clientLibsCommonSections) const libraryPath = '/kotlin' @@ -25,6 +26,7 @@ export default function KotlinReference(props) { + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/self-hosting-auth/[...slug].tsx b/apps/docs/pages/reference/self-hosting-auth/[...slug].tsx index 723d11e079b..fbd09b75644 100644 --- a/apps/docs/pages/reference/self-hosting-auth/[...slug].tsx +++ b/apps/docs/pages/reference/self-hosting-auth/[...slug].tsx @@ -1,11 +1,13 @@ -import authSpec from '~/spec/auth_v1_openapi.json' assert { type: 'json' } -import selfHostingAuthCommonSections from '~/spec/common-self-hosting-auth-sections.json' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import RefSectionHandler from '~/components/reference/RefSectionHandler' import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' import { gen_v3 } from '~/lib/refGenerator/helpers' +import authSpec from '~/spec/auth_v1_openapi.json' assert { type: 'json' } +import selfHostingAuthCommonSections from '~/spec/common-self-hosting-auth-sections.json' + const sections = flattenSections(selfHostingAuthCommonSections) const libraryPath = '/self-hosting-auth' @@ -13,7 +15,15 @@ const libraryPath = '/self-hosting-auth' const spec = gen_v3(authSpec, 'wat', { apiUrl: 'apiv0' }) export default function SelfHostAuthReference(props) { - return + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/self-hosting-functions/[...slug].tsx b/apps/docs/pages/reference/self-hosting-functions/[...slug].tsx index bbd3f335313..432e1a6cec0 100644 --- a/apps/docs/pages/reference/self-hosting-functions/[...slug].tsx +++ b/apps/docs/pages/reference/self-hosting-functions/[...slug].tsx @@ -1,16 +1,23 @@ -import selfHostingFunctionsCommonSections from '~/spec/common-self-hosting-functions-sections.json' - +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import RefSectionHandler from '~/components/reference/RefSectionHandler' import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' -import { gen_v3 } from '~/lib/refGenerator/helpers' + +import selfHostingFunctionsCommonSections from '~/spec/common-self-hosting-functions-sections.json' assert { type: 'json' } const sections = flattenSections(selfHostingFunctionsCommonSections) const libraryPath = '/self-hosting-functions' export default function SelfHostFunctionsReference(props) { - return + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/self-hosting-realtime/[...slug].tsx b/apps/docs/pages/reference/self-hosting-realtime/[...slug].tsx index 59c9276e5b7..3b894c15f8b 100644 --- a/apps/docs/pages/reference/self-hosting-realtime/[...slug].tsx +++ b/apps/docs/pages/reference/self-hosting-realtime/[...slug].tsx @@ -1,15 +1,23 @@ -import selfHostingRealtimeCommonSections from '~/spec/common-self-hosting-realtime-sections.json' - +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import RefSectionHandler from '~/components/reference/RefSectionHandler' import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import selfHostingRealtimeCommonSections from '~/spec/common-self-hosting-realtime-sections.json' assert { type: 'json' } + const sections = flattenSections(selfHostingRealtimeCommonSections) const libraryPath = '/self-hosting-realtime' export default function SelfHostRealtimeReference(props) { - return + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/self-hosting-storage/[...slug].tsx b/apps/docs/pages/reference/self-hosting-storage/[...slug].tsx index 1468b40f3a9..13559bdc7dc 100644 --- a/apps/docs/pages/reference/self-hosting-storage/[...slug].tsx +++ b/apps/docs/pages/reference/self-hosting-storage/[...slug].tsx @@ -1,11 +1,13 @@ -import storageSpec from '~/spec/storage_v0_openapi.json' assert { type: 'json' } -import selfHostingStorageCommonSections from '~/spec/common-self-hosting-storage-sections.json' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' import RefSectionHandler from '~/components/reference/RefSectionHandler' import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' import { gen_v3 } from '~/lib/refGenerator/helpers' +import storageSpec from '~/spec/storage_v0_openapi.json' assert { type: 'json' } +import selfHostingStorageCommonSections from '~/spec/common-self-hosting-storage-sections.json' assert { type: 'json' } + const sections = flattenSections(selfHostingStorageCommonSections) const libraryPath = '/self-hosting-storage' @@ -13,7 +15,15 @@ const libraryPath = '/self-hosting-storage' const spec = gen_v3(storageSpec, 'wat', { apiUrl: 'apiv0' }) export default function SelfHostStorageReference(props) { - return + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/swift/[...slug].tsx b/apps/docs/pages/reference/swift/[...slug].tsx index 6c84806c676..c459f833e11 100644 --- a/apps/docs/pages/reference/swift/[...slug].tsx +++ b/apps/docs/pages/reference/swift/[...slug].tsx @@ -4,12 +4,21 @@ import RefSectionHandler from '~/components/reference/RefSectionHandler' import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' const sections = flattenSections(clientLibsCommonSections) const libraryPath = '/swift' export default function SwiftReference(props) { - return + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/swift/crawlers/[...slug].tsx b/apps/docs/pages/reference/swift/crawlers/[...slug].tsx index d8e3be4f79c..69000ea9674 100644 --- a/apps/docs/pages/reference/swift/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/swift/crawlers/[...slug].tsx @@ -7,6 +7,7 @@ import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' import { useRouter } from 'next/router' import RefSEO from '~/components/reference/RefSEO' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' const sections = flattenSections(clientLibsCommonSections) const libraryPath = '/swift' @@ -25,6 +26,7 @@ export default function SwiftReference(props) { + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/swift/v0/crawlers/[...slug].tsx b/apps/docs/pages/reference/swift/v1/crawlers/[...slug].tsx similarity index 92% rename from apps/docs/pages/reference/swift/v0/crawlers/[...slug].tsx rename to apps/docs/pages/reference/swift/v1/crawlers/[...slug].tsx index 6c19debf234..1ef50177ac2 100644 --- a/apps/docs/pages/reference/swift/v0/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/swift/v1/crawlers/[...slug].tsx @@ -7,6 +7,7 @@ import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' import { useRouter } from 'next/router' import RefSEO from '~/components/reference/RefSEO' +import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu' const sections = flattenSections(clientLibsCommonSections) const libraryPath = '/swift/v2' @@ -24,6 +25,7 @@ export default function SwiftReference(props) { x.length > 0).length ?? 0 - const onExecuteSQL = useCallback(() => { // clean up the sql before sending const policy = editorRef.current?.getValue().replaceAll(' ', ' ') diff --git a/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx b/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx index 5813fef3ad8..1bf095ff73a 100644 --- a/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx +++ b/apps/studio/components/interfaces/Auth/Users/AddUserDropdown.tsx @@ -15,8 +15,8 @@ import { import { useCheckPermissions } from 'hooks' import { IS_PLATFORM } from 'lib/constants' -import CreateUserModal from './CreateUserModal' import InviteUserModal from './InviteUserModal' +import CreateUserModal from './CreateUserModal' export type AddUserDropdownProps = { projectKpsVersion?: string diff --git a/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx b/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx index 4b083ad80f0..5d13dee4588 100644 --- a/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx +++ b/apps/studio/components/interfaces/Database/Backups/PITR/PITRStatus.tsx @@ -61,6 +61,7 @@ const PITRStatus = ({
    + {/* [Joshen TODO] Double check if this is intentional */} - {!canTriggerPhysicalBackup && ( - - - -
    - - You need additional permissions to trigger a PITR recovery - -
    -
    -
    - )} + {hasReadReplicas || + (!canTriggerPhysicalBackup && ( + + + +
    + + {hasReadReplicas + ? 'You will need to remove all read replicas first to trigger a PITR recovery' + : !canTriggerPhysicalBackup + ? 'You need additional permissions to trigger a PITR recovery' + : null} + +
    +
    +
    + ))}
    } diff --git a/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx b/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx index 19587afcb72..ea316a695ac 100644 --- a/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx +++ b/apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx @@ -20,6 +20,7 @@ import { useTheme } from 'next-themes' import 'reactflow/dist/style.css' import { IconLoader } from 'ui' import { TABLE_NODE_ROW_HEIGHT, TABLE_NODE_WIDTH, TableNode } from 'ui-patterns/SchemaTableNode' +import SchemaGraphLegend from './SchemaGraphLegend' type TableNodeData = { name: string @@ -259,6 +260,7 @@ const TablesGraph = ({ tables }: { tables: PostgresTable[] }) => { maskColor={miniMapMaskColor} className="border rounded-md shadow-sm" /> +
    diff --git a/apps/studio/components/interfaces/Database/Schemas/SchemaGraphLegend.tsx b/apps/studio/components/interfaces/Database/Schemas/SchemaGraphLegend.tsx new file mode 100644 index 00000000000..acd94d2380a --- /dev/null +++ b/apps/studio/components/interfaces/Database/Schemas/SchemaGraphLegend.tsx @@ -0,0 +1,38 @@ +import { DiamondIcon, Fingerprint, Hash, Key } from 'lucide-react' + +const SchemaGraphLegend = () => { + return ( +
    + {/*

    Legend

    */} +
      +
    • + + Primary key +
    • +
    • + + Identity +
    • +
    • + + Unique +
    • +
    • + + Nullable +
    • +
    • + + Non-Nullable +
    • +
    +
    + ) +} + +export default SchemaGraphLegend diff --git a/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts b/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts index edfb80c697c..7293d31270b 100644 --- a/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts +++ b/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts @@ -155,6 +155,20 @@ export const FRAMEWORKS: ConnectionType[] = [ }, ], }, + { + key: 'exporeactnative', + label: 'Expo React Native', + icon: 'expo', + guideLink: 'https://supabase.com/docs/guides/getting-started/tutorials/with-expo-react-native', + children: [ + { + key: 'supabasejs', + label: 'Supabase-js', + children: [], + icon: 'supabase', + }, + ], + }, ] export const ORMS: ConnectionType[] = [ diff --git a/apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx b/apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx index 06fea207496..4f13bec8806 100644 --- a/apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx +++ b/apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx @@ -13,7 +13,7 @@ const ConnectionIcon = ({ connection }: ConnectionIconProps) => { { + return ( + + + + + + + + + + {` +SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import 'react-native-url-polyfill/auto' +import AsyncStorage from '@react-native-async-storage/async-storage' +import { createClient } from '@supabase/supabase-js' +import { SUPABASE_URL, SUPABASE_ANON_KEY } from "@env"; + +export const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { + auth: { + storage: AsyncStorage, + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: false, + }, +}) + `} + + + + + + {` +import React, { useState, useEffect } from 'react'; +import { View, Text, FlatList } from 'react-native'; +import { supabase } from '../utils/supabase'; + +export default function App() { + const [todos, setTodos] = useState([]); + + useEffect(() => { + const getTodos = async () => { + try { + const { data: todos, error } = await supabase.from('todos').select(); + + if (error) { + console.error('Error fetching todos:', error.message); + return; + } + + if (todos && todos.length > 0) { + setTodos(todos); + } + } catch (error) { + console.error('Error fetching todos:', error.message); + } + }; + + getTodos(); + }, []); + + return ( + + Todo List + item.id.toString()} + renderItem={({ item }) => {item.title}} + /> + + ); +}; + +`} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx index e8273722604..12e6fd61219 100644 --- a/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx +++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectList.tsx @@ -43,6 +43,7 @@ const ProjectList = ({ search, rewriteHref }: ProjectListProps) => { const projectsByOrg = groupBy(allProjects, 'organization_id') const isLoadingPermissions = IS_PLATFORM ? _isLoadingPermissions : false const noResults = + search.length > 0 && allProjects !== undefined && allProjects.filter((project) => { return ( diff --git a/apps/studio/components/interfaces/HomePageActions.tsx b/apps/studio/components/interfaces/HomePageActions.tsx index 613181ec8d7..dae381e1fcc 100644 --- a/apps/studio/components/interfaces/HomePageActions.tsx +++ b/apps/studio/components/interfaces/HomePageActions.tsx @@ -54,7 +54,7 @@ const HomePageActions = ({ - {IS_PLATFORM && organizationCreationEnabled && orgsLoaded && organizations.length !== 0 && ( + {IS_PLATFORM && organizationCreationEnabled && orgsLoaded && ( diff --git a/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx b/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx index ad437a22520..dde4f3d36ad 100644 --- a/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx +++ b/apps/studio/components/interfaces/Realtime/Inspector/MessageSelection.tsx @@ -1,7 +1,10 @@ +import { useTelemetryProps } from 'common' +import { useRouter } from 'next/router' import { useMemo } from 'react' import { Button, IconX, cn } from 'ui' import CopyButton from 'components/ui/CopyButton' +import Telemetry from 'lib/telemetry' import { LogData } from './Messages.types' import { SelectedRealtimeMessagePanel } from './SelectedRealtimeMessagePanel' @@ -11,6 +14,9 @@ export interface MessageSelectionProps { } const MessageSelection = ({ log, onClose }: MessageSelectionProps) => { + const telemetryProps = useTelemetryProps() + const router = useRouter() + const selectionText = useMemo(() => { return JSON.stringify(log, null, 2) }, [log]) @@ -64,7 +70,22 @@ const MessageSelection = ({ log, onClose }: MessageSelectionProps) => {
    - + { + Telemetry.sendEvent( + { + category: 'realtime_inspector', + action: 'copied_message', + label: 'realtime_inspector_results', + }, + telemetryProps, + router + ) + }} + />
    ) diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx index 74387b88bda..490f135e622 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx @@ -5,7 +5,7 @@ import { Button, IconAlignLeft, IconCommand, IconCornerDownLeft } from 'ui' import { RoleImpersonationPopover } from 'components/interfaces/RoleImpersonationSelector' import DatabaseSelector from 'components/ui/DatabaseSelector' -import { useFlag, useSelectedProject } from 'hooks' +import { useSelectedProject } from 'hooks' import FavoriteButton from './FavoriteButton' import SavingIndicator from './SavingIndicator' @@ -28,9 +28,7 @@ const UtilityActions = ({ }: UtilityActionsProps) => { const os = detectOS() const project = useSelectedProject() - const readReplicasEnabled = useFlag('readReplicas') - - const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled + const showReadReplicasUI = project?.is_read_replicas_enabled return ( <> diff --git a/apps/studio/components/interfaces/Settings/API/ServiceList.tsx b/apps/studio/components/interfaces/Settings/API/ServiceList.tsx index 37ba646a136..72c50f94124 100644 --- a/apps/studio/components/interfaces/Settings/API/ServiceList.tsx +++ b/apps/studio/components/interfaces/Settings/API/ServiceList.tsx @@ -8,19 +8,19 @@ import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectConte import DatabaseSelector from 'components/ui/DatabaseSelector' import Panel from 'components/ui/Panel' import { DisplayApiSettings } from 'components/ui/ProjectSettings' +import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' import { configKeys } from 'data/config/keys' import { useProjectApiQuery } from 'data/config/project-api-query' import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query' +import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useFlag, useStore } from 'hooks' +import { useStore } from 'hooks' +import { PROJECT_STATUS } from 'lib/constants' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { JWT_SECRET_UPDATE_ERROR_MESSAGES } from './API.constants' import JWTSettings from './JWTSettings' import PostgrestConfig from './PostgrestConfig' -import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' -import { PROJECT_STATUS } from 'lib/constants' -import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query' const ServiceList = () => { const { ui } = useStore() @@ -29,8 +29,7 @@ const ServiceList = () => { const { ref: projectRef, source } = useParams() const state = useDatabaseSelectorStateSnapshot() - const readReplicasEnabled = useFlag('readReplicas') - const showReadReplicasUI = readReplicasEnabled && project?.is_read_replicas_enabled + const showReadReplicasUI = project?.is_read_replicas_enabled const { data: settings, isError } = useProjectApiQuery({ projectRef, @@ -91,7 +90,7 @@ const ServiceList = () => { }, [jwtSecretUpdateStatus]) useEffect(() => { - if (readReplicasEnabled && source !== undefined) { + if (showReadReplicasUI && source !== undefined) { state.setSelectedDatabaseId('load-balancer') } }, [source]) diff --git a/apps/studio/components/interfaces/Settings/Addons/Addons.tsx b/apps/studio/components/interfaces/Settings/Addons/Addons.tsx index e8eae68ac94..293cbfb7b13 100644 --- a/apps/studio/components/interfaces/Settings/Addons/Addons.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/Addons.tsx @@ -386,7 +386,7 @@ const Addons = () => {
    -

    IPv4 address

    +

    Dedicated IPv4 address

    More information

    @@ -405,31 +405,6 @@ const Addons = () => {
    - - - - PGBouncer and IPv4 Deprecation on January, 26th - - -

    - Direct connections via db.{projectRef}.supabase.co will resolve to an IPv6 - address starting from January 26th. If you plan on not using our connection - pooler and your environment does not support IPv6, consider enabling the - add-on. -

    -
    - -
    -
    -
    @@ -453,8 +428,8 @@ const Addons = () => {

    Current option:

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

    @@ -470,7 +445,7 @@ const Addons = () => { !(canUpdateIPv4 || ipv4) } > - Change IPv4 address + Change dedicated IPv4 address
    @@ -494,19 +469,6 @@ const Addons = () => { )} - {/* - - */}
    diff --git a/apps/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx index 5771f56abc6..991f0f5477d 100644 --- a/apps/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/ComputeInstanceSidePanel.tsx @@ -35,6 +35,8 @@ import { import * as Tooltip from '@radix-ui/react-tooltip' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { AddonVariantId, ProjectAddonVariantMeta } from 'data/subscriptions/types' +import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' +import { WarningIcon } from 'components/ui/Icons' const ComputeInstanceSidePanel = () => { const queryClient = useQueryClient() @@ -61,6 +63,7 @@ const ComputeInstanceSidePanel = () => { snap.setPanelKey(undefined) } + const { data: databases } = useReadReplicasQuery({ projectRef }) const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const { mutate: updateAddon, isLoading: isUpdating } = useProjectAddonUpdateMutation({ @@ -105,7 +108,6 @@ const ComputeInstanceSidePanel = () => { const [selectedOption, setSelectedOption] = useState('') const isSubmitting = isUpdating || isRemoving - const projectId = selectedProject?.id const cpuArchitecture = getCloudProviderArchitecture(selectedProject?.cloud_provider) const selectedAddons = addons?.selected_addons ?? [] @@ -162,9 +164,12 @@ const ComputeInstanceSidePanel = () => { const selectedCompute = availableOptions.find((option) => option.identifier === selectedOption) const hasChanges = selectedOption !== (subscriptionCompute?.variant.identifier ?? defaultInstanceSize) + const hasReadReplicas = (databases ?? []).length > 1 const blockDowngradeDueToPitr = pitrAddon !== undefined && ['ci_micro', 'ci_nano'].includes(selectedOption) && hasChanges + const blockDowngradeDueToReadReplicas = + hasChanges && hasReadReplicas && ['ci_micro', 'ci_nano'].includes(selectedOption) useEffect(() => { if (visible) { @@ -217,7 +222,12 @@ const ComputeInstanceSidePanel = () => { onConfirm={() => setShowConfirmationModal(true)} loading={isLoading} disabled={ - isFreePlan || isLoading || !hasChanges || blockDowngradeDueToPitr || !canUpdateCompute + isFreePlan || + isLoading || + !hasChanges || + blockDowngradeDueToPitr || + blockDowngradeDueToReadReplicas || + !canUpdateCompute } tooltip={ isFreePlan @@ -382,38 +392,66 @@ const ComputeInstanceSidePanel = () => {

    )} - {hasChanges && !blockDowngradeDueToPitr && ( - - Your project will be unavailable for up to 2 minutes while the changes take place. - + {hasChanges && !blockDowngradeDueToPitr && !blockDowngradeDueToReadReplicas && ( + + + + Your project will need to be restarted when changing it's compute size + + + Your project will be unavailable for up to 2 minutes while the changes take place. + + {hasReadReplicas && ( + + The compute sizes for all read replicas{' '} + on your project will also be changed to the {selectedCompute?.name} size as + well. + + )} + )} - {blockDowngradeDueToPitr && ( - + + + Unable to downgrade as project has active read replicas + + + The minimum compute instance size for using read replicas is the Small Compute. + You need to remove all read replicas before downgrading Compute as it requires at + least a Small compute instance. + + + + + + ) : blockDowngradeDueToPitr ? ( + + + Disable PITR before downgrading + + You currently have PITR enabled. The minimum compute instance size for using PITR + is the Small Compute. + + + You need to disable PITR before downgrading Compute as it requires at least a + Small compute instance. + + - } - > -

    - You currently have PITR enabled. The minimum compute instance size for using PITR - is the Small Compute. -

    -

    - You need to disable PITR before downgrading Compute as it requires at least a - Small compute instance. -

    -
    - )} + + + ) : null} {hasChanges && subscription?.billing_via_partner && diff --git a/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx index 27f518c6d13..3286477e8e3 100644 --- a/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/IPv4SidePanel.tsx @@ -130,14 +130,14 @@ const IPv4SidePanel = () => { } header={
    -

    IPv4 address

    +

    Dedicated IPv4 address

    @@ -147,10 +147,12 @@ const IPv4SidePanel = () => {

    Direct connections to the database only work if your client is able to resolve IPv6 - addresses from January 26th. Enabling the IPv4 add-on allows you to directly connect to - your database via a IPv4 address. If you are connecting via our connection pooler, you - do not need this add-on as our pooler resolves to IPv4 addresses. You can check your - connection info in your{' '} + addresses. Enabling the dedicated IPv4 add-on allows you to directly connect to your + database via a IPv4 address. +

    +

    + If you are connecting via our connection pooler, you do not need this add-on as our + pooler resolves to IPv4 addresses. You can check your connection info in your{' '} project database settings @@ -213,7 +215,7 @@ const IPv4SidePanel = () => { >

    -

    IPv4 address

    +

    Dedicated IPv4 address

    diff --git a/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx b/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx index e539aa53a37..70a052fa4d0 100644 --- a/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx +++ b/apps/studio/components/interfaces/Settings/Addons/PITRSidePanel.tsx @@ -5,14 +5,20 @@ import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import { useParams } from 'common' -import { useTheme } from 'next-themes' import { useProjectAddonRemoveMutation } from 'data/subscriptions/project-addon-remove-mutation' import { useProjectAddonUpdateMutation } from 'data/subscriptions/project-addon-update-mutation' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useCheckPermissions, useSelectedOrganization, useSelectedProject, useStore } from 'hooks' import { BASE_PATH } from 'lib/constants' import Telemetry from 'lib/telemetry' +import { useTheme } from 'next-themes' +import { subscriptionHasHipaaAddon } from 'components/interfaces/Billing/Subscription/Subscription.utils' +import { CriticalIcon, WarningIcon } from 'components/ui/Icons' +import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' +import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' +import { AddonVariantId } from 'data/subscriptions/types' +import { formatCurrency } from 'lib/helpers' import { useSubscriptionPageStateSnapshot } from 'state/subscription-page' import { Alert, @@ -20,16 +26,11 @@ import { AlertTitle_Shadcn_, Alert_Shadcn_, Button, + IconAlertTriangle, IconExternalLink, Radio, SidePanel, - IconAlertTriangle, } from 'ui' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { AlertTriangleIcon } from 'lucide-react' -import { AddonVariantId } from 'data/subscriptions/types' -import { subscriptionHasHipaaAddon } from 'components/interfaces/Billing/Subscription/Subscription.utils' -import { formatCurrency } from 'lib/helpers' const PITR_CATEGORY_OPTIONS: { id: 'off' | 'on' @@ -76,6 +77,7 @@ const PITRSidePanel = () => { snap.setPanelKey(undefined) } + const { data: databases } = useReadReplicasQuery({ projectRef }) const { data: addons, isLoading } = useProjectAddonsQuery({ projectRef }) const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: organization?.slug }) const hasHipaaAddon = subscriptionHasHipaaAddon(subscription) @@ -121,9 +123,12 @@ const PITRSidePanel = () => { const subscriptionPitr = selectedAddons.find((addon) => addon.type === 'pitr') const availableOptions = availableAddons.find((addon) => addon.type === 'pitr')?.variants ?? [] + const hasReadReplicas = (databases ?? []).length > 1 const hasChanges = selectedOption !== (subscriptionPitr?.variant.identifier ?? 'pitr_0') const selectedPitr = availableOptions.find((option) => option.identifier === selectedOption) const isFreePlan = subscription?.plan?.id === 'free' + const blockDowngradeDueToReadReplicas = + hasChanges && hasReadReplicas && selectedCategory === 'off' && selectedOption === 'pitr_0' useEffect(() => { if (visible) { @@ -167,7 +172,13 @@ const PITRSidePanel = () => { onConfirm={onConfirm} loading={isLoading || isSubmitting} disabled={ - isFreePlan || isLoading || !hasChanges || isSubmitting || !canUpdatePitr || hasHipaaAddon + isFreePlan || + isLoading || + !hasChanges || + isSubmitting || + !canUpdatePitr || + hasHipaaAddon || + blockDowngradeDueToReadReplicas } tooltip={ hasHipaaAddon @@ -225,7 +236,13 @@ const PITRSidePanel = () => { className={clsx('col-span-3 group space-y-1', isFreePlan && 'opacity-75')} onClick={() => { setSelectedCategory(option.id) - if (option.id === 'off') setSelectedOption('pitr_0') + if (option.id === 'off') { + setSelectedOption('pitr_0') + } else if (subscriptionPitr?.variant.identifier !== undefined) { + setSelectedOption(subscriptionPitr.variant.identifier) + } else { + setSelectedOption('pitr_7') + } Telemetry.sendActivity( { activity: 'Option Selected', @@ -270,7 +287,7 @@ const PITRSidePanel = () => { {selectedCategory === 'off' && subscriptionPitr !== undefined && isBranchingEnabled && ( - + Are you sure you want to disable this while using Branching? @@ -281,6 +298,25 @@ const PITRSidePanel = () => { )} + {blockDowngradeDueToReadReplicas && ( + + + Remove all read replicas before downgrading + + You currently have active read replicas. The minimum compute instance size for using + read replicas is the Small Compute. You need to remove all read replicas before + downgrading Compute as it requires at least a Small compute instance. + + + + + + )} + {selectedCategory === 'on' && (

    {isFreePlan ? ( @@ -358,7 +394,7 @@ const PITRSidePanel = () => {
    )} - {hasChanges && ( + {hasChanges && !blockDowngradeDueToReadReplicas && ( <> {selectedOption === 'pitr_0' || (selectedPitr?.price ?? 0) < (subscriptionPitr?.variant.price ?? 0) ? ( diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx index 6815fbe195b..78e327fd269 100644 --- a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx +++ b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx @@ -23,9 +23,10 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useProjectSettingsQuery } from 'data/config/project-settings-query' import { usePoolingConfigurationQuery } from 'data/database/pooling-configuration-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' -import { useFlag } from 'hooks' import { pluckObjectFields } from 'lib/helpers' import Telemetry from 'lib/telemetry' +import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' +import { useDatabaseSettingsStateSnapshot } from 'state/database-settings' import { IPv4DeprecationNotice } from '../IPv4DeprecationNotice' import { UsePoolerCheckbox } from '../UsePoolerCheckbox' import { @@ -51,15 +52,14 @@ interface DatabaseConnectionStringProps { export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStringProps) => { const router = useRouter() - const { project: projectDetails, isLoading: isProjectLoading } = useProjectContext() - const { ref: projectRef, connectionString } = useParams() const telemetryProps = useTelemetryProps() - const readReplicasEnabled = useFlag('readReplicas') && projectDetails?.is_read_replicas_enabled + const { ref: projectRef, connectionString } = useParams() + const snap = useDatabaseSettingsStateSnapshot() + const state = useDatabaseSelectorStateSnapshot() + const { project: projectDetails, isLoading: isProjectLoading } = useProjectContext() + const readReplicasEnabled = projectDetails?.is_read_replicas_enabled const connectionStringsRef = useRef(null) - // [Joshen] Temp - so that behaviour for is similar to usePoolerCheckbox where the state is not synced with Connection Params - const [selectedDatabaseId, setSelectedDatabaseId] = useState(projectRef) - const [usePoolerConnection, setUsePoolerConnection] = useState(true) const [poolingMode, setPoolingMode] = useState<'transaction' | 'session' | 'statement'>('session') const [selectedTab, setSelectedTab] = useState< 'uri' | 'psql' | 'golang' | 'jdbc' | 'dotnet' | 'nodejs' | 'php' | 'python' @@ -69,7 +69,7 @@ export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStrin projectRef, }) const poolingConfiguration = readReplicasEnabled - ? poolingInfo?.find((x) => x.identifier === selectedDatabaseId) + ? poolingInfo?.find((x) => x.identifier === state.selectedDatabaseId) : poolingInfo?.find((x) => x.database_type === 'PRIMARY') const { @@ -93,7 +93,9 @@ export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStrin const isError = readReplicasEnabled ? isErrorReadReplicas : isErrorProjectSettings const isSuccess = readReplicasEnabled ? isSuccessReadReplicas : isSuccessProjectSettings - const selectedDatabase = (databases ?? []).find((db) => db.identifier === selectedDatabaseId) + const selectedDatabase = (databases ?? []).find( + (db) => db.identifier === state.selectedDatabaseId + ) const { project } = data ?? {} const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at'] @@ -123,7 +125,7 @@ export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStrin isSuccessPoolingInfo && poolingConfiguration !== undefined ? getConnectionStrings(connectionInfo, poolingConfiguration, { projectRef, - usePoolerConnection, + usePoolerConnection: snap.usePoolerConnection, }) : { uri: '', psql: '', golang: '', jdbc: '', dotnet: '', nodejs: '', php: '', python: '' } const poolerTld = @@ -134,12 +136,12 @@ export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStrin isSuccessPoolingInfo && poolingConfiguration !== undefined ? constructConnStringSyntax(poolingConfiguration?.connectionString, { selectedTab, - usePoolerConnection, + usePoolerConnection: snap.usePoolerConnection, ref: projectRef as string, cloudProvider: isProjectLoading ? '' : project?.cloud_provider || '', region: isProjectLoading ? '' : project?.region || '', - tld: usePoolerConnection ? poolerTld : connectionTld, - portNumber: usePoolerConnection + tld: snap.usePoolerConnection ? poolerTld : connectionTld, + portNumber: snap.usePoolerConnection ? poolingMode === 'transaction' ? poolingConfiguration?.db_port.toString() : '5432' @@ -153,7 +155,7 @@ export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStrin connectionString !== undefined && connectionStringsRef.current !== undefined ) { - setSelectedDatabaseId(connectionString) + state.setSelectedDatabaseId(connectionString) connectionStringsRef.current?.scrollIntoView({ block: 'center', behavior: 'smooth' }) } }, [connectionString]) @@ -195,12 +197,7 @@ export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStrin appearance === 'minimal' ? 'absolute -top-1 right-0' : '' )} > - {readReplicasEnabled && ( - - )} + {readReplicasEnabled && } + + + )} - {AVAILABLE_REPLICA_REGIONS.map((region) => ( + {availableRegions.map((region) => ( void + onCancel: () => void +} + +const DropAllReplicasConfirmationModal = ({ + visible, + onSuccess, + onCancel, +}: DropAllReplicasConfirmationModalProps) => { + const { ref: projectRef } = useParams() + const queryClient = useQueryClient() + const { data: databases } = useReadReplicasQuery({ projectRef }) + const { mutateAsync: removeReadReplica, isLoading: isRemoving } = useReadReplicaRemoveMutation() + + const onConfirmRemove = async () => { + if (!projectRef) return console.error('Project is required') + if (databases === undefined) return console.error('Unable to retrieve replicas') + if (databases.length === 1) toast('Your project has no read replicas') + + const replicas = databases.filter((db) => db.identifier !== projectRef) + await Promise.all( + replicas.map((db) => + removeReadReplica({ projectRef, identifier: db.identifier, skipInvalidateOnSuccess: true }) + ) + ) + toast.success(`Tearing down all read replicas`) + + queryClient.setQueriesData(replicaKeys.list(projectRef), (old: Database[]) => { + return old.filter((db: Database) => db.identifier === projectRef) + }) + queryClient.setQueriesData(replicaKeys.loadBalancers(projectRef), (old: Database[]) => []) + + setTimeout(async () => { + await queryClient.invalidateQueries(replicaKeys.list(projectRef)) + await queryClient.invalidateQueries(replicaKeys.loadBalancers(projectRef)) + }, 5000) + + onSuccess() + onCancel() + } + + return ( + onCancel()} + onSelectConfirm={() => onConfirmRemove()} + > + + + + This action cannot be undone + + You may still deploy new replicas in this region thereafter + + +
    +

    Before deleting all replicas, consider:

    +
      +
    • Network traffic from this region may slow down
    • +
    +
    +
    +
    + ) +} + +export default DropAllReplicasConfirmationModal diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx index 9a9dcb09ccf..319b7f661c8 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/DropReplicaConfirmationModal.tsx @@ -1,8 +1,4 @@ import { useParams } from 'common' -import ConfirmationModal from 'components/ui/ConfirmationModal' -import { useReadReplicaRemoveMutation } from 'data/read-replicas/replica-remove-mutation' -import { Database } from 'data/read-replicas/replicas-query' -import { formatDatabaseID } from 'data/read-replicas/replicas.utils' import toast from 'react-hot-toast' import { AlertDescription_Shadcn_, @@ -12,6 +8,11 @@ import { Modal, } from 'ui' +import ConfirmationModal from 'components/ui/ConfirmationModal' +import { useReadReplicaRemoveMutation } from 'data/read-replicas/replica-remove-mutation' +import { Database } from 'data/read-replicas/replicas-query' +import { formatDatabaseID } from 'data/read-replicas/replicas.utils' + interface DropReplicaConfirmationModalProps { selectedReplica?: Database onSuccess: () => void @@ -25,9 +26,9 @@ const DropReplicaConfirmationModal = ({ }: DropReplicaConfirmationModalProps) => { const { ref: projectRef } = useParams() const formattedId = formatDatabaseID(selectedReplica?.identifier ?? '') - const { mutateAsync: removeReadReplica } = useReadReplicaRemoveMutation({ + const { mutateAsync: removeReadReplica, isLoading: isRemoving } = useReadReplicaRemoveMutation({ onSuccess: () => { - toast.success(`Successfully removed read replica (ID: ${formattedId})`) + toast.success(`Tearing down read replica (ID: ${formattedId})`) onSuccess() onCancel() }, @@ -44,6 +45,7 @@ const DropReplicaConfirmationModal = ({ { const { resolvedTheme } = useTheme() const { ref: projectRef } = useParams() const numComingUp = useRef() + const snap = useSubscriptionPageStateSnapshot() const [view, setView] = useState<'flow' | 'map'>('flow') + const [showDeleteAllModal, setShowDeleteAllModal] = useState(false) const [showNewReplicaPanel, setShowNewReplicaPanel] = useState(false) const [refetchInterval, setRefetchInterval] = useState(10000) const [newReplicaRegion, setNewReplicaRegion] = useState() - const [selectedReplicaToResize, setSelectedReplicaToResize] = useState() const [selectedReplicaToDrop, setSelectedReplicaToDrop] = useState() const [selectedReplicaToRestart, setSelectedReplicaToRestart] = useState() @@ -59,14 +72,14 @@ const InstanceConfigurationUI = () => { { refetchInterval: refetchInterval as any, refetchOnWindowFocus: false, - onSuccess: (data) => { + onSuccess: async (data) => { const comingUpReplicas = data.filter((db) => db.status === 'COMING_UP') const hasTransientStatus = comingUpReplicas.length > 0 // If any replica's status has changed, refetch databases if (numComingUp.current !== comingUpReplicas.length) { numComingUp.current = comingUpReplicas.length - refetch() + await refetch() } // If all replicas are active healthy, stop fetching statuses @@ -85,35 +98,42 @@ const InstanceConfigurationUI = () => { isSuccessReplicas && isSuccessLoadBalancers ? generateNodes(primary, replicas, loadBalancers ?? [], { onSelectRestartReplica: setSelectedReplicaToRestart, - onSelectResizeReplica: setSelectedReplicaToResize, onSelectDropReplica: setSelectedReplicaToDrop, }) : [], - [isSuccessReplicas, isSuccessLoadBalancers] + [isSuccessReplicas, isSuccessLoadBalancers, primary, replicas, loadBalancers] ) - const edges: Edge[] = [ - ...((loadBalancers ?? []).length > 0 - ? [ - { - id: `load-balancer-${primary.identifier}`, - source: 'load-balancer', - target: primary.identifier, - type: 'smoothstep', - animated: true, - }, - ] - : []), - ...replicas.map((database) => { - return { - id: `${primary.identifier}-${database.identifier}`, - source: primary.identifier, - target: database.identifier, - type: 'smoothstep', - animated: true, - } - }), - ] + const edges: Edge[] = useMemo( + () => + isSuccessReplicas && isSuccessLoadBalancers + ? [ + ...((loadBalancers ?? []).length > 0 + ? [ + { + id: `load-balancer-${primary.identifier}`, + source: 'load-balancer', + target: primary.identifier, + type: 'smoothstep', + animated: true, + className: '!cursor-default', + }, + ] + : []), + ...replicas.map((database) => { + return { + id: `${primary.identifier}-${database.identifier}`, + source: primary.identifier, + target: database.identifier, + type: 'smoothstep', + animated: true, + className: '!cursor-default', + } + }), + ] + : [], + [isSuccessLoadBalancers, isSuccessReplicas, loadBalancers, primary?.identifier, replicas] + ) const nodeTypes = useMemo( () => ({ @@ -125,24 +145,23 @@ const InstanceConfigurationUI = () => { [] ) - const onConfirmDropReplica = () => { - console.log('Drop replica', selectedReplicaToDrop) - } + const setReactFlow = async () => { + const graph = getDagreGraphLayout(nodes, edges) + const { nodes: updatedNodes } = addRegionNodes(graph.nodes, graph.edges) + reactFlow.setNodes(updatedNodes) + reactFlow.setEdges(graph.edges) - const onConfirmRestartReplica = () => { - console.log('Restart replica', selectedReplicaToRestart) + // [Joshen] Odd fix to ensure that react flow snaps back to center when adding nodes + await timeout(1) + reactFlow.fitView({ maxZoom: 0.9, minZoom: 0.9 }) } // [Joshen] Just FYI this block is oddly triggering whenever we refocus on the viewport // even if I change the dependency array to just data. Not blocker, just an area to optimize useEffect(() => { - if (isSuccessReplicas && isSuccessLoadBalancers && replicas.length > 0) { - const graph = getDagreGraphLayout(nodes, edges) - const { nodes: updatedNodes } = addRegionNodes(graph.nodes, graph.edges) - reactFlow.setNodes(updatedNodes) - reactFlow.setEdges(graph.edges) - } - }, [isSuccessReplicas, isSuccessLoadBalancers]) + if (isSuccessReplicas && isSuccessLoadBalancers && nodes.length > 0 && view === 'flow') + setReactFlow() + }, [isSuccessReplicas, isSuccessLoadBalancers, nodes, edges, view]) return ( <> @@ -156,9 +175,33 @@ const InstanceConfigurationUI = () => { {isSuccessReplicas && ( <>
    - +
    + + + +
    - )} - -
    - } - loading={isLoading} - disabled={isLoading} - onClick={onRefresh} - > - Refresh - + } + loading={isLoading} + disabled={isLoading} + onClick={onRefresh} + /> + + + Refresh logs + + ) + const handleDatepickerChange = ({ to, from }: Partial[1]>) => { onSearch('datepicker-change', { to, from }) } + const handleInputSearch = (query: string) => onSearch('search-input-change', { query }) return (
    -
    +
    + + -
    - -
    + {FILTER_OPTIONS[table] !== undefined && ( +
    + {FILTER_OPTIONS[table] && + Object.values(FILTER_OPTIONS[table]).map((x, i: number) => { + const classes = [] -
    - {FILTER_OPTIONS[table] && - Object.values(FILTER_OPTIONS[table]).map((x, i: number) => { - const classes = [] - - if (Object.values(FILTER_OPTIONS[table]).length >= 2) { - if (i === 0) { - classes.push('rounded-tr-none rounded-br-none') - } else if (i === Object.values(FILTER_OPTIONS[table]).length - 1) { - classes.push('rounded-tl-none rounded-bl-none') - } else { - classes.push('rounded-none') + if (Object.values(FILTER_OPTIONS[table]).length >= 2) { + if (i === 0) { + classes.push('rounded-tr-none rounded-br-none') + } else if (i === Object.values(FILTER_OPTIONS[table]).length - 1) { + classes.push('rounded-tl-none rounded-bl-none') + } else { + classes.push('rounded-none') + } } - } - return ( - - ) - })} -
    + return ( + + ) + })} +
    + )}
    - + {showDatabaseSelector ? ( +
    + + + + + + Open query in Logs Explorer + + + +
    + ) : ( + + )}
    ) } diff --git a/apps/studio/components/interfaces/Support/SupportForm.tsx b/apps/studio/components/interfaces/Support/SupportForm.tsx index 81258d829c8..7545bda0b21 100644 --- a/apps/studio/components/interfaces/Support/SupportForm.tsx +++ b/apps/studio/components/interfaces/Support/SupportForm.tsx @@ -1,6 +1,5 @@ import { useParams } from 'common' import { CLIENT_LIBRARIES } from 'common/constants' -import { observer } from 'mobx-react-lite' import Link from 'next/link' import { useRouter } from 'next/router' import { ChangeEvent, useEffect, useRef, useState } from 'react' @@ -28,17 +27,17 @@ import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { getProjectAuthConfig } from 'data/auth/auth-config-query' import { useSendSupportTicketMutation } from 'data/feedback/support-ticket-send' import { useOrganizationsQuery } from 'data/organizations/organizations-query' +import { Project } from 'data/projects/project-detail-query' import { useProjectsQuery } from 'data/projects/projects-query' +import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useFlag, useStore } from 'hooks' import useLatest from 'hooks/misc/useLatest' import { detectBrowser } from 'lib/helpers' import { useProfile } from 'lib/profile' +import { HelpCircle } from 'lucide-react' import DisabledStateForFreeTier from './DisabledStateForFreeTier' import { CATEGORY_OPTIONS, SERVICE_OPTIONS, SEVERITY_OPTIONS } from './Support.constants' import { formatMessage, uploadAttachments } from './SupportForm.utils' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' -import { Project } from 'data/projects/project-detail-query' -import { HelpCircle } from 'lucide-react' const MAX_ATTACHMENTS = 5 const INCLUDE_DISCUSSIONS = ['Problem', 'Database_unresponsive'] @@ -122,16 +121,6 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => { orgSlug: selectedOrganizationSlug, }) - useEffect(() => { - if (!uploadedFiles) return - const objectUrls = uploadedFiles.map((file) => URL.createObjectURL(file)) - setUploadedDataUrls(objectUrls) - - return () => { - objectUrls.forEach((url: any) => URL.revokeObjectURL(url)) - } - }, [uploadedFiles]) - const { profile } = useProfile() const respondToEmail = profile?.primary_email ?? 'your email' @@ -239,6 +228,23 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => { const ipv4MigrationStringMatched = ipv4MigrationStrings.some((str) => textAreaValue.includes(str)) + useEffect(() => { + if (!uploadedFiles) return + const objectUrls = uploadedFiles.map((file) => URL.createObjectURL(file)) + setUploadedDataUrls(objectUrls) + + return () => { + objectUrls.forEach((url: any) => URL.revokeObjectURL(url)) + } + }, [uploadedFiles]) + + useEffect(() => { + if (isSuccessProjects && ref !== undefined) { + const selectedProjectFromUrl = projects.find((project) => project.ref === ref) + if (selectedProjectFromUrl !== undefined) setSelectedProjectRef(selectedProjectFromUrl.ref) + } + }, [isSuccessProjects]) + return (
    {({ resetForm, values }: any) => { @@ -295,7 +301,12 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => { } resetForm({ values: updatedValues, initialValues: updatedValues }) } - }, [isSuccessProjects, isSuccessOrganizations]) + }, [ + isSuccessProjects, + isSuccessOrganizations, + selectedProjectRef, + selectedOrganizationSlug, + ]) // Populate fields when router is ready, required when navigating to // support form on a refresh browser session @@ -821,4 +832,4 @@ const SupportForm = ({ setSentCategory }: SupportFormProps) => { ) } -export default observer(SupportForm) +export default SupportForm diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx index a5b13a8aa1f..0d63b7684fb 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx @@ -40,6 +40,11 @@ import { import ColumnForeignKey from './ColumnForeignKey' import ColumnType from './ColumnType' import HeaderTitle from './HeaderTitle' +import { + CONSTRAINT_TYPE, + Constraint, + useTableConstraintsQuery, +} from 'data/database/constraints-query' export interface ColumnEditorProps { column?: PostgresColumn @@ -50,7 +55,8 @@ export interface ColumnEditorProps { payload: CreateColumnPayload | UpdateColumnPayload, isNewRecord: boolean, configuration: { - columnId: string | undefined + columnId?: string + primaryKey?: Constraint }, resolve: any ) => void @@ -80,6 +86,16 @@ const ColumnEditor = ({ (type) => !EXCLUDED_SCHEMAS_WITHOUT_EXTENSIONS.includes(type.schema) ) + const { data: constraints } = useTableConstraintsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + schema: selectedTable?.schema, + table: selectedTable?.name, + }) + const primaryKey = (constraints ?? []).find( + (constraint) => constraint.type === CONSTRAINT_TYPE.PRIMARY_KEY_CONSTRAINT + ) + const { data } = useForeignKeyConstraintsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, @@ -131,7 +147,7 @@ const ColumnEditor = ({ const payload = isNewRecord ? generateCreateColumnPayload(selectedTable.id, columnFields) : generateUpdateColumnPayload(column!, selectedTable, columnFields) - const configuration = { columnId: column?.id } + const configuration = { columnId: column?.id, primaryKey } saveChanges(payload, isNewRecord, configuration, resolve) } else { resolve() diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index bfacdc7e05e..b0f85d5ac50 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -38,6 +38,7 @@ import { updateTable, } from './SidePanelEditor.utils' import { ImportContent } from './TableEditor/TableEditor.types' +import { Constraint } from 'data/database/constraints-query' export interface SidePanelEditorProps { editable?: boolean @@ -201,11 +202,11 @@ const SidePanelEditor = ({ const saveColumn = async ( payload: CreateColumnPayload | UpdateColumnPayload, isNewRecord: boolean, - configuration: { columnId?: string }, + configuration: { columnId?: string; primaryKey?: Constraint }, resolve: any ) => { const selectedColumnToEdit = snap.sidePanel?.type === 'column' && snap.sidePanel.column - const { columnId } = configuration + const { columnId, primaryKey } = configuration const response = isNewRecord ? await createColumn({ @@ -213,6 +214,7 @@ const SidePanelEditor = ({ connectionString: project?.connectionString, payload: payload as CreateColumnPayload, selectedTable: selectedTable as PostgresTable, + primaryKey, }) : await updateColumn({ projectRef: project?.ref!, @@ -220,6 +222,7 @@ const SidePanelEditor = ({ id: columnId as string, payload: payload as UpdateColumnPayload, selectedTable: selectedTable as PostgresTable, + primaryKey, }) if (response?.error) { @@ -361,6 +364,7 @@ const SidePanelEditor = ({ isRealtimeEnabled: boolean isDuplicateRows: boolean existingForeignKeyRelations: ForeignKeyConstraint[] + primaryKey?: Constraint }, resolve: any ) => { @@ -372,6 +376,7 @@ const SidePanelEditor = ({ isRealtimeEnabled, isDuplicateRows, existingForeignKeyRelations, + primaryKey, } = configuration try { @@ -436,6 +441,7 @@ const SidePanelEditor = ({ columns, foreignKeyRelations, existingForeignKeyRelations, + primaryKey, }) await updateTableRealtime(table, isRealtimeEnabled) diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx index 7587564ac8b..0ad5c98e484 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.utils.tsx @@ -33,6 +33,7 @@ import { ForeignKey } from './ForeignKeySelector/ForeignKeySelector.types' import { ColumnField, CreateColumnPayload, UpdateColumnPayload } from './SidePanelEditor.types' import { checkIfRelationChanged } from './TableEditor/ForeignKeysManagement/ForeignKeysManagement.utils' import { ImportContent } from './TableEditor/TableEditor.types' +import { Constraint } from 'data/database/constraints-query' const BATCH_SIZE = 1000 const CHUNK_SIZE = 1024 * 1024 * 0.1 // 0.1MB @@ -112,18 +113,19 @@ export const addPrimaryKey = async ( }) } -export const removePrimaryKey = async ( +export const dropConstraint = async ( projectRef: string, connectionString: string | undefined, schema: string, - table: string + table: string, + name: string ) => { - const query = `ALTER TABLE "${schema}"."${table}" DROP CONSTRAINT "${table}_pkey"` + const query = `ALTER TABLE "${schema}"."${table}" DROP CONSTRAINT "${name}"` return await executeSql({ projectRef: projectRef, connectionString: connectionString, sql: query, - queryKey: ['primary-keys'], + queryKey: ['drop-constraint'], }) } @@ -266,11 +268,13 @@ export const createColumn = async ({ connectionString, payload, selectedTable, + primaryKey, }: { projectRef: string connectionString: string | undefined payload: CreateColumnPayload selectedTable: PostgresTable + primaryKey?: Constraint }) => { const toastId = toast.loading(`Creating column "${payload.name}"...`) try { @@ -288,8 +292,14 @@ export const createColumn = async ({ // Same logic in createTable: Remove any primary key constraints first (we'll add it back later) const existingPrimaryKeys = selectedTable.primary_keys.map((x) => x.name) - if (existingPrimaryKeys.length > 0) { - await removePrimaryKey(projectRef, connectionString, column.schema, column.table) + if (existingPrimaryKeys.length > 0 && primaryKey !== undefined) { + await dropConstraint( + projectRef, + connectionString, + column.schema, + column.table, + primaryKey.name + ) } const primaryKeyColumns = existingPrimaryKeys.concat([column.name]) @@ -314,6 +324,7 @@ export const updateColumn = async ({ id, payload, selectedTable, + primaryKey, skipPKCreation, skipSuccessMessage = false, }: { @@ -322,6 +333,7 @@ export const updateColumn = async ({ id: string payload: UpdateColumnPayload selectedTable: PostgresTable + primaryKey?: Constraint skipPKCreation?: boolean skipSuccessMessage?: boolean }) => { @@ -338,8 +350,14 @@ export const updateColumn = async ({ const existingPrimaryKeys = selectedTable.primary_keys.map((x) => x.name) // Primary key is getting updated for the column - if (existingPrimaryKeys.length > 0) { - await removePrimaryKey(projectRef, connectionString, column.schema, column.table) + if (existingPrimaryKeys.length > 0 && primaryKey !== undefined) { + await dropConstraint( + projectRef, + connectionString, + column.schema, + column.table, + primaryKey.name + ) } const primaryKeyColumns = isPrimaryKey @@ -632,6 +650,7 @@ export const updateTable = async ({ columns, foreignKeyRelations, existingForeignKeyRelations, + primaryKey, }: { projectRef: string connectionString: string | undefined @@ -641,6 +660,7 @@ export const updateTable = async ({ columns: ColumnField[] foreignKeyRelations: ForeignKey[] existingForeignKeyRelations: ForeignKeyConstraint[] + primaryKey?: Constraint }) => { // Prepare a check to see if primary keys to the tables were updated or not const primaryKeyColumns = columns @@ -655,8 +675,8 @@ export const updateTable = async ({ // If we do it later, and if the user deleted a PK column, we'd need to do // an additional check when removing PK if the column in the PK was removed // So doing this one step earlier, lets us skip that additional check. - if (table.primary_keys.length > 0) { - await removePrimaryKey(projectRef, connectionString, table.schema, table.name) + if (primaryKey !== undefined) { + await dropConstraint(projectRef, connectionString, table.schema, table.name, primaryKey.name) } } diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index 2433c8c413c..d69ee875b54 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -32,6 +32,11 @@ import { generateTableFieldFromPostgresTable, validateFields, } from './TableEditor.utils' +import { + CONSTRAINT_TYPE, + Constraint, + useTableConstraintsQuery, +} from 'data/database/constraints-query' export interface TableEditorProps { table?: PostgresTable @@ -54,6 +59,7 @@ export interface TableEditorProps { isRealtimeEnabled: boolean isDuplicateRows: boolean existingForeignKeyRelations: ForeignKeyConstraint[] + primaryKey?: Constraint }, resolve: any ) => void @@ -103,6 +109,16 @@ const TableEditor = ({ const [isImportingSpreadsheet, setIsImportingSpreadsheet] = useState(false) const [rlsConfirmVisible, setRlsConfirmVisible] = useState(false) + const { data: constraints } = useTableConstraintsQuery({ + projectRef: project?.ref, + connectionString: project?.connectionString, + schema: table?.schema, + table: table?.name, + }) + const primaryKey = (constraints ?? []).find( + (constraint) => constraint.type === CONSTRAINT_TYPE.PRIMARY_KEY_CONSTRAINT + ) + const { data: foreignKeyMeta } = useForeignKeyConstraintsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, @@ -173,6 +189,7 @@ const TableEditor = ({ isRealtimeEnabled: tableFields.isRealtimeEnabled, isDuplicateRows: isDuplicateRows, existingForeignKeyRelations: foreignKeys, + primaryKey, } saveChanges(payload, tableFields.columns, fkRelations, isNewRecord, configuration, resolve) diff --git a/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx b/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx index 51ae83afd01..beca9292de8 100644 --- a/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx +++ b/apps/studio/components/layouts/AppLayout/OrganizationDropdown.tsx @@ -47,7 +47,7 @@ const OrganizationDropdown = ({ isNewNav = false }: OrganizationDropdownProps) = } return ( -
    +
    diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx index 90aa9060350..cfe9255be01 100644 --- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx +++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.tsx @@ -39,6 +39,10 @@ import { generateToolRoutes, } from './NavigationBar.utils' import NavigationIconButton from './NavigationIconButton' +import { Home } from 'icons' + +export const ICON_SIZE = 20 +export const ICON_STROKE_WIDTH = 1.5 const NavigationBar = () => { const os = detectOS() @@ -96,7 +100,7 @@ const NavigationBar = () => { route={{ key: 'HOME', label: 'Home', - icon: , + icon: , link: `/project/${projectRef}`, }} /> diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx index 09af36377a6..79d0bf961ae 100644 --- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx +++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx @@ -1,10 +1,19 @@ -import SVG from 'react-inlinesvg' -import { products } from 'shared-data' -import { IconBarChart, IconFileText, IconList, IconSettings } from 'ui' - import { Route } from 'components/ui/ui.types' import { Project } from 'data/projects/project-detail-query' -import { BASE_PATH, IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' +import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' +import { + Auth, + Database, + EdgeFunctions, + Realtime, + Reports, + Settings, + SqlEditor, + Storage, + TableEditor, +} from 'icons' +import { ICON_SIZE, ICON_STROKE_WIDTH } from './NavigationBar' +import { FileText, List } from 'lucide-react' export const generateToolRoutes = (ref?: string, project?: Project): Route[] => { const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP @@ -14,25 +23,13 @@ export const generateToolRoutes = (ref?: string, project?: Project): Route[] => { key: 'editor', label: 'Table Editor', - icon: ( - code.replace(/svg/, 'svg class="m-auto text-color-inherit"')} - /> - ), + icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/editor`), }, { key: 'sql', label: 'SQL Editor', - icon: ( - code.replace(/svg/, 'svg class="m-auto text-color-inherit"')} - /> - ), + icon: , link: !IS_PLATFORM ? `/project/${ref}/sql/1` : ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/sql/new`), @@ -56,24 +53,7 @@ export const generateProductRoutes = ( { key: 'database', label: 'Database', - icon: ( - - - - ), + icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/database/tables`), }, ...(authEnabled @@ -81,24 +61,7 @@ export const generateProductRoutes = ( { key: 'auth', label: 'Authentication', - icon: ( - - - - ), + icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/auth/users`), }, ] @@ -108,24 +71,7 @@ export const generateProductRoutes = ( { key: 'storage', label: 'Storage', - icon: ( - - - - ), + icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/storage/buckets`), }, ] @@ -135,51 +81,17 @@ export const generateProductRoutes = ( { key: 'functions', label: 'Edge Functions', - icon: ( - - - - ), + icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/functions`), }, ] : []), - ...(IS_PLATFORM && realtimeEnabled + ...(realtimeEnabled ? [ { key: 'realtime', label: 'Realtime', - icon: ( - - - - ), + icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/realtime/inspector`), }, ] @@ -197,7 +109,7 @@ export const generateOtherRoutes = (ref?: string, project?: Project): Route[] => { key: 'reports', label: 'Reports', - icon: , + icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/reports`), }, ] @@ -205,13 +117,13 @@ export const generateOtherRoutes = (ref?: string, project?: Project): Route[] => { key: 'logs', label: 'Logs', - icon: , + icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/logs/explorer`), }, { key: 'api', label: 'API Docs', - icon: , + icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/api`), }, ...(IS_PLATFORM @@ -219,7 +131,7 @@ export const generateOtherRoutes = (ref?: string, project?: Project): Route[] => { key: 'settings', label: 'Project Settings', - icon: , + icon: , link: ref && `/project/${ref}/settings/general`, }, ] diff --git a/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx b/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx index 1bf0e8b733f..dcd5ad39405 100644 --- a/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx +++ b/apps/studio/components/layouts/ProjectLayout/ProductMenuBar.tsx @@ -8,7 +8,7 @@ const ProductMenuBar = ({ title, children }: PropsWithChildren) => { const router = useRouter() const { ref: projectRef } = useParams() @@ -105,26 +108,46 @@ const ProjectLayout = ({ {/* Left-most navigation side bar to access products */} {!hideIconBar && } {/* Product menu bar */} - {!showPausedState && ( - - {productMenu} - - )} -
    - {!navLayoutV2 && !hideHeader && IS_PLATFORM && } - {showPausedState ? ( -
    -
    - -
    -
    - ) : ( - - - {children} - + + {!showPausedState && productMenu && ( + <> + + + {productMenu} + + + + )} - + +
    + {!navLayoutV2 && !hideHeader && IS_PLATFORM && } + {showPausedState ? ( +
    +
    + +
    +
    + ) : ( + + + {children} + + )} +
    +
    +
    diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.tsx b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.tsx index 3a00e1d1214..b20474dba70 100644 --- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.tsx +++ b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorLayout.tsx @@ -17,6 +17,7 @@ const SQLEditorLayout = ({ title, children }: SQLEditorLayoutProps) => { product="SQL Editor" productMenu={productMenu} isBlocking={false} + resizableSidebar > {children} diff --git a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx index 2be0ab6e683..4ceef5b5b4f 100644 --- a/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx +++ b/apps/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx @@ -314,7 +314,7 @@ const SideBarContent = observer(() => { onClick={() => setSelectedQueries([])} /> - + Clear selection diff --git a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx index 60eef71bc66..f72e396098a 100644 --- a/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/EntityListItem.tsx @@ -118,161 +118,148 @@ const EntityListItem: ItemRenderer = ({ } return ( -
    - - - - {entity.type === ENTITY_TYPE.TABLE ? ( - - ) : entity.type === ENTITY_TYPE.VIEW ? ( - - ) : ( -
    - {Object.entries(ENTITY_TYPE) - .find(([, value]) => value === entity.type)?.[0]?.[0] - ?.toUpperCase()} -
    - )} -
    - - - - {formatTooltipText(entity.type)} - - -
    -
    - {/* only show tooltips if required, to reduce noise */} - {entity.name.length > 20 ? ( - - - {entity.name} - - - - - {entity.name} - - - + + + {entity.type === ENTITY_TYPE.TABLE ? ( + + ) : entity.type === ENTITY_TYPE.VIEW ? ( + ) : ( - entity.name - )} - - {entity.type === ENTITY_TYPE.TABLE && !entity.rls_enabled && ( -
    - +
    + {Object.entries(ENTITY_TYPE) + .find(([, value]) => value === entity.type)?.[0]?.[0] + ?.toUpperCase()}
    )} -
    - -
    - {entity.type === ENTITY_TYPE.TABLE && isActive && !isLocked && ( - - -
    - -
    -
    - - { - e.stopPropagation() - snap.onEditTable() - }} - > - - Edit Table - - { - e.stopPropagation() - snap.onDuplicateTable() - }} - > - - Duplicate Table - - - - - View Policies - - - { - e.stopPropagation() - exportTableAsCSV() - }} - > - - Export as CSV - - - { - e.stopPropagation() - snap.onDeleteTable() - }} - > - - Delete Table - - -
    + + + + + {formatTooltipText(entity.type)} + + + +
    + + {entity.name} + + {entity.type === ENTITY_TYPE.TABLE && !entity.rls_enabled && ( + )}
    -
    + + {entity.type === ENTITY_TYPE.TABLE && isActive && !isLocked && ( + + + + + + { + e.stopPropagation() + snap.onEditTable() + }} + > + + Edit Table + + { + e.stopPropagation() + snap.onDuplicateTable() + }} + > + + Duplicate Table + + + + + View Policies + + + { + e.stopPropagation() + exportTableAsCSV() + }} + > + + Export as CSV + + + { + e.stopPropagation() + snap.onDeleteTable() + }} + > + + Delete Table + + + + )} + ) } diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx index 9ca18c1fb5d..abeb9fb5339 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx @@ -23,7 +23,12 @@ const TableEditorLayout = ({ children }: PropsWithChildren<{}>) => { } return ( - + {children} ) diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index 1f04cfc7fbb..6d2afdd80df 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -1,9 +1,8 @@ import * as Tooltip from '@radix-ui/react-tooltip' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' -import { AnimatePresence, motion } from 'framer-motion' import { partition } from 'lodash' -import { Loader2 } from 'lucide-react' +import { Plus, Search } from 'lucide-react' import { useRouter } from 'next/router' import { useEffect, useMemo, useRef, useState } from 'react' @@ -18,9 +17,7 @@ import { DropdownMenuRadioItem, DropdownMenuTrigger, IconChevronsDown, - IconPlusCircle, - IconSearch, - IconX, + Input_Shadcn_, cn, } from 'ui' @@ -119,197 +116,139 @@ const TableEditorMenu = () => { return ( <>
    - { - setSearchText('') - snap.setSelectedSchemaName(name) - router.push(`/project/${project?.ref}/editor`) - }} - onSelectCreateSchema={() => snap.onAddSchema()} - /> +
    + { + setSearchText('') + snap.setSelectedSchemaName(name) + router.push(`/project/${project?.ref}/editor`) + }} + onSelectCreateSchema={() => snap.onAddSchema()} + /> -
    - {!isLocked ? ( - - -
    - } - type="default" - className="justify-start h-7" - onClick={snap.onAddTable} - > - New table - - - {!canCreateTables && ( - - - -
    - - You need additional permissions to create tables - -
    -
    -
    - )} - - ) : ( - - Viewing protected schema - -

    - This schema is managed by Supabase and is read-only through the table editor -

    - -
    -
    - )} -
    - -
    -
    -
    - - {isSearchOpen && ( - + {!isLocked ? ( + + +
    -
    - - - - - - - + New table + + + {!canCreateTables && ( - + - Sort By +
    + + You need additional permissions to create tables + +
    -
    - - - setSort(value)} + )} + + ) : ( + + + Viewing protected schema + + +

    + This schema is managed by Supabase and is read-only through the table editor +

    + +
    +
    + )} +
    +
    +
    +
    + + + + + + + + + + - - Alphabetical - - - Entity Type - - - - -
    + + Sort By + + + + + setSort(value)}> + + Alphabetical + + + Entity Type + + + +
    {isLoading && ( diff --git a/apps/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.tsx b/apps/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.tsx index 451c70485b8..d1b725d7477 100644 --- a/apps/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.tsx +++ b/apps/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.tsx @@ -6,9 +6,13 @@ import { Button, DropdownMenu, DropdownMenuContent, - DropdownMenuSeparator, DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, DropdownMenuTrigger, + IconCheck, IconChevronLeft, IconChevronRight, IconColumns, @@ -21,18 +25,14 @@ import { IconUpload, IconX, Input, - IconCheck, - DropdownMenuSub, - DropdownMenuSubTrigger, - DropdownMenuSubContent, } from 'ui' +import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' +import APIDocsButton from 'components/ui/APIDocsButton' import { useCheckPermissions } from 'hooks' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' import { useStorageExplorerStateSnapshot } from 'state/storage-explorer' import { STORAGE_SORT_BY, STORAGE_SORT_BY_ORDER, STORAGE_VIEWS } from '../Storage.constants' -import APIDocsButton from 'components/ui/APIDocsButton' -import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' const VIEW_OPTIONS = [ { key: STORAGE_VIEWS.COLUMNS, name: 'As columns' }, diff --git a/apps/studio/components/ui/CSVButton.tsx b/apps/studio/components/ui/CSVButton.tsx index 3289641afe0..36db8de8ea2 100644 --- a/apps/studio/components/ui/CSVButton.tsx +++ b/apps/studio/components/ui/CSVButton.tsx @@ -1,8 +1,13 @@ -import { Button, IconDownloadCloud } from 'ui' -import { ButtonProps } from 'ui/src/components/Button/Button' -import { flattenDeep } from 'lodash' import React, { PropsWithChildren, useMemo, useRef } from 'react' import { CSVLink } from 'react-csv' +import { + Button, + IconDownload, + TooltipContent_Shadcn_, + TooltipTrigger_Shadcn_, + Tooltip_Shadcn_, +} from 'ui' +import { ButtonProps } from 'ui/src/components/Button/Button' interface CSVButtonProps { buttonType?: ButtonProps['type'] @@ -52,17 +57,25 @@ const CSVButton = ({ filename={`supabase_logs.csv`} title={title} /> - + + + + + + Download logs as CSV + + ) } diff --git a/apps/studio/components/ui/DatabaseSelector.tsx b/apps/studio/components/ui/DatabaseSelector.tsx index 6fe27c61c8e..ab43c7fa5ba 100644 --- a/apps/studio/components/ui/DatabaseSelector.tsx +++ b/apps/studio/components/ui/DatabaseSelector.tsx @@ -1,7 +1,8 @@ +import { useParams } from 'common' +import { noop } from 'lodash' import Link from 'next/link' import { useRouter } from 'next/router' import { useState } from 'react' -import { useParams } from 'common' import { Button, CommandGroup_Shadcn_, @@ -10,6 +11,7 @@ import { Command_Shadcn_, IconCheck, IconChevronDown, + IconLoader, IconPlus, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, @@ -25,25 +27,26 @@ import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' interface DatabaseSelectorProps { variant?: 'regular' | 'connected-on-right' | 'connected-on-left' | 'connected-on-both' additionalOptions?: { id: string; name: string }[] - selectedId?: string // Optional to let parent component control its state (e.g Database Settings page) - setSelectedId?: (value: string) => void + onSelectId?: (id: string) => void // Optional callback } const DatabaseSelector = ({ variant = 'regular', additionalOptions = [], - selectedId, - setSelectedId, + onSelectId = noop, }: DatabaseSelectorProps) => { const router = useRouter() const { ref: projectRef } = useParams() const [open, setOpen] = useState(false) const state = useDatabaseSelectorStateSnapshot() - const selectedDatabaseId = selectedId || state.selectedDatabaseId + const selectedDatabaseId = state.selectedDatabaseId - const { data } = useReadReplicasQuery({ projectRef }) + const { data, isLoading, isSuccess } = useReadReplicasQuery({ projectRef }) const databases = data ?? [] + const sortedDatabases = databases + .sort((a, b) => (a.inserted_at > b.inserted_at ? 1 : 0)) + .sort((database) => (database.identifier === projectRef ? -1 : 0)) const selectedDatabase = databases.find((db) => db.identifier === selectedDatabaseId) const selectedDatabaseRegion = formatDatabaseRegion(selectedDatabase?.region ?? '') @@ -63,6 +66,7 @@ const DatabaseSelector = ({ variant === 'connected-on-left' && 'rounded-l-none border-l-0', variant === 'connected-on-both' && 'rounded-none border-x-0' )} + icon={isLoading && } iconRight={ } @@ -73,11 +77,11 @@ const DatabaseSelector = ({ ) : ( <> - {selectedDatabase?.identifier === projectRef + {isLoading || selectedDatabase?.identifier === projectRef ? 'Primary database' : 'Read replica'} {' '} - {selectedDatabase?.identifier !== projectRef && ( + {isSuccess && selectedDatabase?.identifier !== projectRef && ( ({selectedDatabaseRegion} - {formattedDatabaseId}) @@ -116,7 +120,7 @@ const DatabaseSelector = ({ )} 7 ? 'h-[210px]' : ''}> - {databases?.map((database) => { + {sortedDatabases?.map((database) => { const region = formatDatabaseRegion(database.region) const id = formatDatabaseID(database.identifier) @@ -126,14 +130,14 @@ const DatabaseSelector = ({ value={database.identifier} className="cursor-pointer w-full" onSelect={() => { - if (setSelectedId) setSelectedId(database.identifier) - else state.setSelectedDatabaseId(database.identifier) + state.setSelectedDatabaseId(database.identifier) setOpen(false) + onSelectId(database.identifier) }} onClick={() => { - if (setSelectedId) setSelectedId(database.identifier) - else state.setSelectedDatabaseId(database.identifier) + state.setSelectedDatabaseId(database.identifier) setOpen(false) + onSelectId(database.identifier) }} >
    @@ -160,9 +164,7 @@ const DatabaseSelector = ({ > { - setOpen(false) - }} + onClick={() => setOpen(false)} className="w-full flex items-center gap-2" > diff --git a/apps/studio/components/ui/Logs/LogsExplorerHeader.tsx b/apps/studio/components/ui/Logs/LogsExplorerHeader.tsx index 2202d62f3cc..e2ae887e49b 100644 --- a/apps/studio/components/ui/Logs/LogsExplorerHeader.tsx +++ b/apps/studio/components/ui/Logs/LogsExplorerHeader.tsx @@ -1,7 +1,4 @@ import * as Tooltip from '@radix-ui/react-tooltip' -import { LOGS_EXPLORER_DOCS_URL } from 'components/interfaces/Settings/Logs' -import Table from 'components/to-be-cleaned/Table' -import { copyToClipboard } from 'lib/helpers' import Link from 'next/link' import { useState } from 'react' import { logConstants } from 'shared-data' @@ -17,6 +14,10 @@ import { Tabs, } from 'ui' +import { LOGS_EXPLORER_DOCS_URL } from 'components/interfaces/Settings/Logs' +import Table from 'components/to-be-cleaned/Table' +import { copyToClipboard } from 'lib/helpers' + export interface LogsExplorerHeaderProps { subtitle?: string } diff --git a/apps/studio/components/ui/SchemaSelector.tsx b/apps/studio/components/ui/SchemaSelector.tsx index f5f57ecfc55..a6a01069364 100644 --- a/apps/studio/components/ui/SchemaSelector.tsx +++ b/apps/studio/components/ui/SchemaSelector.tsx @@ -23,6 +23,7 @@ import { import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { useSchemasQuery } from 'data/database/schemas-query' +import { ChevronDownIcon, ChevronsUpDown } from 'lucide-react' interface SchemaSelectorProps { className?: string @@ -65,16 +66,8 @@ const SchemaSelector = ({ return (
    {isSchemasLoading && ( - )} @@ -98,15 +91,16 @@ const SchemaSelector = ({ -
    -
    -
    - ) -}) - -export default index diff --git a/packages/ui/src/layout/banners/index.tsx b/packages/ui/src/layout/banners/index.tsx index 8ba69528077..49a7d6ec582 100644 --- a/packages/ui/src/layout/banners/index.tsx +++ b/packages/ui/src/layout/banners/index.tsx @@ -1,3 +1,2 @@ export { default as Announcement } from './Announcement' -export { default as IPV4Banner } from './IPV4Banner' export { default as LW8CountdownBanner } from './LW8CountdownBanner/LW8CountdownBanner' diff --git a/vale/styles/Custom/AMPM.yml b/vale/styles/Custom/AMPM.yml new file mode 100644 index 00000000000..fbdc6e4f84b --- /dev/null +++ b/vale/styles/Custom/AMPM.yml @@ -0,0 +1,9 @@ +extends: existence +message: "Use 'AM' or 'PM' (preceded by a space)." +link: 'https://developers.google.com/style/word-list' +level: error +nonword: true +tokens: + - '\d{1,2}[AP]M' + - '\d{1,2} ?[ap]m' + - '\d{1,2} ?[aApP]\.[mM]\.' diff --git a/vale/styles/Custom/Acronyms.yml b/vale/styles/Custom/Acronyms.yml new file mode 100644 index 00000000000..2fdabe6af4c --- /dev/null +++ b/vale/styles/Custom/Acronyms.yml @@ -0,0 +1,68 @@ +extends: conditional +message: "Spell out '%s', if it's unfamiliar to the audience." +link: 'https://developers.google.com/style/abbreviations' +level: suggestion +ignorecase: false +# Ensures that the existence of 'first' implies the existence of 'second'. +first: '\b([A-Z]{3,5})\b' +second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)' +# ... with the exception of these: +exceptions: + - API + - ASP + - CLI + - CPU + - CSS + - CSV + - DEBUG + - DOM + - DPI + - FAQ + - GCC + - GDB + - GET + - GPT + - GPU + - GTK + - GUI + - HTML + - HTTP + - HTTPS + - IDE + - IMDB + - JAR + - JSON + - JSX + - JWT + - LESS + - LLDB + - NET + - NOTE + - NVDA + - OSS + - PATH + - PDF + - PHP + - POST + - RAM + - REPL + - REST + - RSA + - SCM + - SCSS + - SDK + - SQL + - SSH + - SSL + - SVG + - TBD + - TCP + - TODO + - URI + - URL + - USB + - UTF + - XML + - XSS + - YAML + - ZIP diff --git a/vale/styles/Custom/Colons.yml b/vale/styles/Custom/Colons.yml new file mode 100644 index 00000000000..99363fbd46d --- /dev/null +++ b/vale/styles/Custom/Colons.yml @@ -0,0 +1,8 @@ +extends: existence +message: "'%s' should be in lowercase." +link: 'https://developers.google.com/style/colons' +nonword: true +level: warning +scope: sentence +tokens: + - ':\s[A-Z]' diff --git a/vale/styles/Custom/Contractions.yml b/vale/styles/Custom/Contractions.yml new file mode 100644 index 00000000000..4f6fd5d489d --- /dev/null +++ b/vale/styles/Custom/Contractions.yml @@ -0,0 +1,30 @@ +extends: substitution +message: "Use '%s' instead of '%s'." +link: 'https://developers.google.com/style/contractions' +level: suggestion +ignorecase: true +action: + name: replace +swap: + are not: aren't + cannot: can't + could not: couldn't + did not: didn't + do not: don't + does not: doesn't + has not: hasn't + have not: haven't + how is: how's + is not: isn't + it is: it's + should not: shouldn't + that is: that's + they are: they're + was not: wasn't + we are: we're + we have: we've + were not: weren't + what is: what's + when is: when's + where is: where's + will not: won't diff --git a/vale/styles/Custom/DateFormat.yml b/vale/styles/Custom/DateFormat.yml new file mode 100644 index 00000000000..e9d227fa13d --- /dev/null +++ b/vale/styles/Custom/DateFormat.yml @@ -0,0 +1,9 @@ +extends: existence +message: "Use 'July 31, 2016' format, not '%s'." +link: 'https://developers.google.com/style/dates-times' +ignorecase: true +level: error +nonword: true +tokens: + - '\d{1,2}(?:\.|/)\d{1,2}(?:\.|/)\d{4}' + - '\d{1,2} (?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)|May|Jun(?:e)|Jul(?:y)|Aug(?:ust)|Sep(?:tember)?|Oct(?:ober)|Nov(?:ember)?|Dec(?:ember)?) \d{4}' diff --git a/vale/styles/Custom/Ellipses.yml b/vale/styles/Custom/Ellipses.yml new file mode 100644 index 00000000000..1e070517bfe --- /dev/null +++ b/vale/styles/Custom/Ellipses.yml @@ -0,0 +1,9 @@ +extends: existence +message: "In general, don't use an ellipsis." +link: 'https://developers.google.com/style/ellipses' +nonword: true +level: warning +action: + name: remove +tokens: + - '\.\.\.' diff --git a/vale/styles/Custom/EmDash.yml b/vale/styles/Custom/EmDash.yml new file mode 100644 index 00000000000..5a81fb0942e --- /dev/null +++ b/vale/styles/Custom/EmDash.yml @@ -0,0 +1,13 @@ +extends: existence +message: "Don't put a space before or after a dash." +link: "https://developers.google.com/style/dashes" +nonword: true +level: error +action: + name: edit + params: + - trim + - " " +tokens: + - '\s[—–]\s' + diff --git a/vale/styles/Custom/Exclamation.yml b/vale/styles/Custom/Exclamation.yml new file mode 100644 index 00000000000..b4e4a1f7e9a --- /dev/null +++ b/vale/styles/Custom/Exclamation.yml @@ -0,0 +1,12 @@ +extends: existence +message: "Don't use exclamation points in text." +link: "https://developers.google.com/style/exclamation-points" +nonword: true +level: error +action: + name: edit + params: + - trim_right + - "!" +tokens: + - '\w+!(?:\s|$)' diff --git a/vale/styles/Custom/FirstPerson.yml b/vale/styles/Custom/FirstPerson.yml new file mode 100644 index 00000000000..0b7b8828ca5 --- /dev/null +++ b/vale/styles/Custom/FirstPerson.yml @@ -0,0 +1,13 @@ +extends: existence +message: "Avoid first-person pronouns such as '%s'." +link: 'https://developers.google.com/style/pronouns#personal-pronouns' +ignorecase: true +level: warning +nonword: true +tokens: + - (?:^|\s)I\s + - (?:^|\s)I,\s + - \bI'm\b + - \bme\b + - \bmy\b + - \bmine\b diff --git a/vale/styles/Custom/Gender.yml b/vale/styles/Custom/Gender.yml new file mode 100644 index 00000000000..c8486181d69 --- /dev/null +++ b/vale/styles/Custom/Gender.yml @@ -0,0 +1,9 @@ +extends: existence +message: "Don't use '%s' as a gender-neutral pronoun." +link: 'https://developers.google.com/style/pronouns#gender-neutral-pronouns' +level: error +ignorecase: true +tokens: + - he/she + - s/he + - \(s\)he diff --git a/vale/styles/Custom/GenderBias.yml b/vale/styles/Custom/GenderBias.yml new file mode 100644 index 00000000000..9e701908630 --- /dev/null +++ b/vale/styles/Custom/GenderBias.yml @@ -0,0 +1,47 @@ +extends: substitution +message: "Consider using '%s' instead of '%s'." +link: 'https://developers.google.com/style/inclusive-documentation' +ignorecase: true +level: error +action: + name: replace +swap: + (?:alumna|alumnus): graduate + (?:alumnae|alumni): graduates + air(?:m[ae]n|wom[ae]n): pilot(s) + anchor(?:m[ae]n|wom[ae]n): anchor(s) + authoress: author + camera(?:m[ae]n|wom[ae]n): camera operator(s) + chair(?:m[ae]n|wom[ae]n): chair(s) + congress(?:m[ae]n|wom[ae]n): member(s) of congress + door(?:m[ae]|wom[ae]n): concierge(s) + draft(?:m[ae]n|wom[ae]n): drafter(s) + fire(?:m[ae]n|wom[ae]n): firefighter(s) + fisher(?:m[ae]n|wom[ae]n): fisher(s) + fresh(?:m[ae]n|wom[ae]n): first-year student(s) + garbage(?:m[ae]n|wom[ae]n): waste collector(s) + lady lawyer: lawyer + ladylike: courteous + landlord: building manager + mail(?:m[ae]n|wom[ae]n): mail carriers + man and wife: husband and wife + man enough: strong enough + mankind: human kind + manmade: manufactured + manpower: personnel + men and girls: men and women + middle(?:m[ae]n|wom[ae]n): intermediary + news(?:m[ae]n|wom[ae]n): journalist(s) + ombuds(?:man|woman): ombuds + oneupmanship: upstaging + poetess: poet + police(?:m[ae]n|wom[ae]n): police officer(s) + repair(?:m[ae]n|wom[ae]n): technician(s) + sales(?:m[ae]n|wom[ae]n): salesperson or sales people + service(?:m[ae]n|wom[ae]n): soldier(s) + steward(?:ess)?: flight attendant + tribes(?:m[ae]n|wom[ae]n): tribe member(s) + waitress: waiter + woman doctor: doctor + woman scientist[s]?: scientist(s) + work(?:m[ae]n|wom[ae]n): worker(s) diff --git a/vale/styles/Custom/HeadingPunctuation.yml b/vale/styles/Custom/HeadingPunctuation.yml new file mode 100644 index 00000000000..c1729868b64 --- /dev/null +++ b/vale/styles/Custom/HeadingPunctuation.yml @@ -0,0 +1,13 @@ +extends: existence +message: "Don't put a period at the end of a heading." +link: "https://developers.google.com/style/capitalization#capitalization-in-titles-and-headings" +nonword: true +level: warning +scope: heading +action: + name: edit + params: + - trim_right + - "." +tokens: + - '[a-z0-9][.]\s*$' diff --git a/vale/styles/Custom/Headings.yml b/vale/styles/Custom/Headings.yml new file mode 100644 index 00000000000..beb9022e6a5 --- /dev/null +++ b/vale/styles/Custom/Headings.yml @@ -0,0 +1,37 @@ +extends: capitalization +message: "'%s' should use sentence-style capitalization." +link: "https://developers.google.com/style/capitalization#capitalization-in-titles-and-headings" +level: warning +scope: heading +match: $sentence +indicators: + - ":" +exceptions: + - API + - Azure + - ChatGPT + - ChatGPT Retrieval Plugin + - CLI + - Cosmos + - Docker + - Edge Functions? + - Emmet + - gRPC + - HNSW + - I + - JWT + - Kubernetes + - Linux + - macOS + - Marketplace + - MongoDB + - OpenAI + - REPL + - REST + - Studio + - Transformers.js + - TypeScript + - URLs + - Visual + - VS + - Windows diff --git a/vale/styles/Custom/Latin.yml b/vale/styles/Custom/Latin.yml new file mode 100644 index 00000000000..ca03b9154b1 --- /dev/null +++ b/vale/styles/Custom/Latin.yml @@ -0,0 +1,11 @@ +extends: substitution +message: "Use '%s' instead of '%s'." +link: 'https://developers.google.com/style/abbreviations' +ignorecase: true +level: error +nonword: true +action: + name: replace +swap: + '\b(?:eg|e\.g\.)(?=[\s,;])': for example + '\b(?:ie|i\.e\.)(?=[\s,;])': that is diff --git a/vale/styles/Custom/LyHyphens.yml b/vale/styles/Custom/LyHyphens.yml new file mode 100644 index 00000000000..50dacb40db8 --- /dev/null +++ b/vale/styles/Custom/LyHyphens.yml @@ -0,0 +1,14 @@ +extends: existence +message: "'%s' doesn't need a hyphen." +link: "https://developers.google.com/style/hyphens" +level: error +ignorecase: false +nonword: true +action: + name: edit + params: + - regex + - "-" + - " " +tokens: + - '\b[^\s-]+ly-\w+\b' diff --git a/vale/styles/Custom/OptionalPlurals.yml b/vale/styles/Custom/OptionalPlurals.yml new file mode 100644 index 00000000000..4a8767d6f55 --- /dev/null +++ b/vale/styles/Custom/OptionalPlurals.yml @@ -0,0 +1,12 @@ +extends: existence +message: "Don't use plurals in parentheses such as in '%s'." +link: "https://developers.google.com/style/plurals-parentheses" +level: error +nonword: true +action: + name: edit + params: + - trim_right + - "(s)" +tokens: + - '\b\w+\(s\)' diff --git a/vale/styles/Custom/Ordinal.yml b/vale/styles/Custom/Ordinal.yml new file mode 100644 index 00000000000..d1ac7d27e80 --- /dev/null +++ b/vale/styles/Custom/Ordinal.yml @@ -0,0 +1,7 @@ +extends: existence +message: "Spell out all ordinal numbers ('%s') in text." +link: 'https://developers.google.com/style/numbers' +level: error +nonword: true +tokens: + - \d+(?:st|nd|rd|th) diff --git a/vale/styles/Custom/OxfordComma.yml b/vale/styles/Custom/OxfordComma.yml new file mode 100644 index 00000000000..b9ba21ebb25 --- /dev/null +++ b/vale/styles/Custom/OxfordComma.yml @@ -0,0 +1,7 @@ +extends: existence +message: "Use the Oxford comma in '%s'." +link: 'https://developers.google.com/style/commas' +scope: sentence +level: warning +tokens: + - '(?:[^,]+,){1,}\s\w+\s(?:and|or)' diff --git a/vale/styles/Custom/Parens.yml b/vale/styles/Custom/Parens.yml new file mode 100644 index 00000000000..3b8711d0c88 --- /dev/null +++ b/vale/styles/Custom/Parens.yml @@ -0,0 +1,7 @@ +extends: existence +message: "Use parentheses judiciously." +link: 'https://developers.google.com/style/parentheses' +nonword: true +level: suggestion +tokens: + - '\(.+\)' diff --git a/vale/styles/Custom/Passive.yml b/vale/styles/Custom/Passive.yml new file mode 100644 index 00000000000..3265890e520 --- /dev/null +++ b/vale/styles/Custom/Passive.yml @@ -0,0 +1,184 @@ +extends: existence +link: 'https://developers.google.com/style/voice' +message: "In general, use active voice instead of passive voice ('%s')." +ignorecase: true +level: suggestion +raw: + - \b(am|are|were|being|is|been|was|be)\b\s* +tokens: + - '[\w]+ed' + - awoken + - beat + - become + - been + - begun + - bent + - beset + - bet + - bid + - bidden + - bitten + - bled + - blown + - born + - bought + - bound + - bred + - broadcast + - broken + - brought + - built + - burnt + - burst + - cast + - caught + - chosen + - clung + - come + - cost + - crept + - cut + - dealt + - dived + - done + - drawn + - dreamt + - driven + - drunk + - dug + - eaten + - fallen + - fed + - felt + - fit + - fled + - flown + - flung + - forbidden + - foregone + - forgiven + - forgotten + - forsaken + - fought + - found + - frozen + - given + - gone + - gotten + - ground + - grown + - heard + - held + - hidden + - hit + - hung + - hurt + - kept + - knelt + - knit + - known + - laid + - lain + - leapt + - learnt + - led + - left + - lent + - let + - lighted + - lost + - made + - meant + - met + - misspelt + - mistaken + - mown + - overcome + - overdone + - overtaken + - overthrown + - paid + - pled + - proven + - put + - quit + - read + - rid + - ridden + - risen + - run + - rung + - said + - sat + - sawn + - seen + - sent + - set + - sewn + - shaken + - shaven + - shed + - shod + - shone + - shorn + - shot + - shown + - shrunk + - shut + - slain + - slept + - slid + - slit + - slung + - smitten + - sold + - sought + - sown + - sped + - spent + - spilt + - spit + - split + - spoken + - spread + - sprung + - spun + - stolen + - stood + - stridden + - striven + - struck + - strung + - stuck + - stung + - stunk + - sung + - sunk + - swept + - swollen + - sworn + - swum + - swung + - taken + - taught + - thought + - thrived + - thrown + - thrust + - told + - torn + - trodden + - understood + - upheld + - upset + - wed + - wept + - withheld + - withstood + - woken + - won + - worn + - wound + - woven + - written + - wrung diff --git a/vale/styles/Custom/Periods.yml b/vale/styles/Custom/Periods.yml new file mode 100644 index 00000000000..d24a6a6c033 --- /dev/null +++ b/vale/styles/Custom/Periods.yml @@ -0,0 +1,7 @@ +extends: existence +message: "Don't use periods with acronyms or initialisms such as '%s'." +link: 'https://developers.google.com/style/abbreviations' +level: error +nonword: true +tokens: + - '\b(?:[A-Z]\.){3,}' diff --git a/vale/styles/Custom/Quotes.yml b/vale/styles/Custom/Quotes.yml new file mode 100644 index 00000000000..3cb6f1abd18 --- /dev/null +++ b/vale/styles/Custom/Quotes.yml @@ -0,0 +1,7 @@ +extends: existence +message: "Commas and periods go inside quotation marks." +link: 'https://developers.google.com/style/quotation-marks' +level: error +nonword: true +tokens: + - '"[^"]+"[.,?]' diff --git a/vale/styles/Custom/Ranges.yml b/vale/styles/Custom/Ranges.yml new file mode 100644 index 00000000000..3ec045e777d --- /dev/null +++ b/vale/styles/Custom/Ranges.yml @@ -0,0 +1,7 @@ +extends: existence +message: "Don't add words such as 'from' or 'between' to describe a range of numbers." +link: 'https://developers.google.com/style/hyphens' +nonword: true +level: warning +tokens: + - '(?:from|between)\s\d+\s?-\s?\d+' diff --git a/vale/styles/Custom/Semicolons.yml b/vale/styles/Custom/Semicolons.yml new file mode 100644 index 00000000000..bb8b85b420e --- /dev/null +++ b/vale/styles/Custom/Semicolons.yml @@ -0,0 +1,8 @@ +extends: existence +message: "Use semicolons judiciously." +link: 'https://developers.google.com/style/semicolons' +nonword: true +scope: sentence +level: suggestion +tokens: + - ';' diff --git a/vale/styles/Custom/Slang.yml b/vale/styles/Custom/Slang.yml new file mode 100644 index 00000000000..63f4c248a84 --- /dev/null +++ b/vale/styles/Custom/Slang.yml @@ -0,0 +1,11 @@ +extends: existence +message: "Don't use internet slang abbreviations such as '%s'." +link: 'https://developers.google.com/style/abbreviations' +ignorecase: true +level: error +tokens: + - 'tl;dr' + - ymmv + - rtfm + - imo + - fwiw diff --git a/vale/styles/Custom/Spacing.yml b/vale/styles/Custom/Spacing.yml new file mode 100644 index 00000000000..66e45a6b72a --- /dev/null +++ b/vale/styles/Custom/Spacing.yml @@ -0,0 +1,10 @@ +extends: existence +message: "'%s' should have one space." +link: 'https://developers.google.com/style/sentence-spacing' +level: error +nonword: true +action: + name: remove +tokens: + - '[a-z][.?!] {2,}[A-Z]' + - '[a-z][.?!][A-Z]' diff --git a/vale/styles/Custom/Spelling.yml b/vale/styles/Custom/Spelling.yml new file mode 100644 index 00000000000..527ac07d318 --- /dev/null +++ b/vale/styles/Custom/Spelling.yml @@ -0,0 +1,10 @@ +extends: existence +message: "In general, use American spelling instead of '%s'." +link: 'https://developers.google.com/style/spelling' +ignorecase: true +level: warning +tokens: + - '(?:\w+)nised?' + - 'colour' + - 'labour' + - 'centre' diff --git a/vale/styles/Custom/Units.yml b/vale/styles/Custom/Units.yml new file mode 100644 index 00000000000..53522ab2dea --- /dev/null +++ b/vale/styles/Custom/Units.yml @@ -0,0 +1,8 @@ +extends: existence +message: "Put a nonbreaking space between the number and the unit in '%s'." +link: "https://developers.google.com/style/units-of-measure" +nonword: true +level: error +tokens: + - \b\d+(?:B|kB|MB|GB|TB) + - \b\d+(?:ns|ms|s|min|h|d) diff --git a/vale/styles/Custom/We.yml b/vale/styles/Custom/We.yml new file mode 100644 index 00000000000..c7ac7d36221 --- /dev/null +++ b/vale/styles/Custom/We.yml @@ -0,0 +1,11 @@ +extends: existence +message: "Try to avoid using first-person plural like '%s'." +link: 'https://developers.google.com/style/pronouns#personal-pronouns' +level: warning +ignorecase: true +tokens: + - we + - we'(?:ve|re) + - ours? + - us + - let's diff --git a/vale/styles/Custom/Will.yml b/vale/styles/Custom/Will.yml new file mode 100644 index 00000000000..128a918362b --- /dev/null +++ b/vale/styles/Custom/Will.yml @@ -0,0 +1,7 @@ +extends: existence +message: "Avoid using '%s'." +link: 'https://developers.google.com/style/tense' +ignorecase: true +level: warning +tokens: + - will diff --git a/vale/styles/Custom/WordList.yml b/vale/styles/Custom/WordList.yml new file mode 100644 index 00000000000..bb53e8f7471 --- /dev/null +++ b/vale/styles/Custom/WordList.yml @@ -0,0 +1,82 @@ +extends: substitution +message: "Use '%s' instead of '%s'." +link: "https://developers.google.com/style/word-list" +level: warning +ignorecase: false +action: + name: replace +swap: + "(?:API Console|dev|developer) key": API key + "(?:cell ?phone|smart ?phone)": phone|mobile phone + "(?:dev|developer|APIs) console": API console + "(?:e-mail|Email|E-mail)": email + "(?:file ?path|path ?name)": path + "(?:kill|terminate|abort)": stop|exit|cancel|end + "(?:OAuth ?2|Oauth)": OAuth 2.0 + "(?:ok|Okay)": OK|okay + "(?:WiFi|wifi)": Wi-Fi + "(?i)postgresql": Postgres + "(?i)repository": repo + '[\.]+apk': APK + '3\-D': 3D + 'Google (?:I\-O|IO)': Google I/O + "tap (?:&|and) hold": touch & hold + "un(?:check|select)": clear + above: preceding + account name: username + action bar: app bar + admin: administrator + Ajax: AJAX + a\.k\.a|aka: or|also known as + Android device: Android-powered device + android: Android + API explorer: APIs Explorer + application: app + approx\.: approximately + authN: authentication + authZ: authorization + autoupdate: automatically update + cellular data: mobile data + cellular network: mobile network + chapter: documents|pages|sections + check box: checkbox + check: select + click on: click|click in + Cloud: Google Cloud Platform|GCP + Container Engine: Kubernetes Engine + content type: media type + curated roles: predefined roles + data are: data is + Developers Console: Google API Console|API Console + disabled?: turn off|off + ephemeral IP address: ephemeral external IP address + fewer data: less data + file name: filename + firewalls: firewall rules + functionality: capability|feature + Google account: Google Account + Google accounts: Google Accounts + Googling: search with Google + grayed-out: unavailable + HTTPs: HTTPS + in order to: to + ingest: import|load + k8s: Kubernetes + long press: touch & hold + network IP address: internal IP address + omnibox: address bar + open-source: open source + overview screen: recents screen + regex: regular expression + SHA1: SHA-1|HAS-SHA1 + sign into: sign in to + sign-?on: single sign-on + static IP address: static external IP address + stylesheet: style sheet + synch: sync + tablename: table name + tablet: device + touch: tap + url: URL + vs\.: versus + World Wide Web: web diff --git a/vale/styles/Custom/meta.json b/vale/styles/Custom/meta.json new file mode 100644 index 00000000000..a5da2a8480e --- /dev/null +++ b/vale/styles/Custom/meta.json @@ -0,0 +1,4 @@ +{ + "feed": "https://github.com/errata-ai/Google/releases.atom", + "vale_version": ">=1.0.0" +} diff --git a/vale/styles/Custom/vocab.txt b/vale/styles/Custom/vocab.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/vale/styles/config/vocabularies/Supabase/accept.txt b/vale/styles/config/vocabularies/Supabase/accept.txt new file mode 100644 index 00000000000..f02c243b654 --- /dev/null +++ b/vale/styles/config/vocabularies/Supabase/accept.txt @@ -0,0 +1,18 @@ +ChatGPT +Clippy +Colab +CPUs? +[Dd]eduplicat(e|ion) +GPUs? +IVFFlat +OpenAI +[Pp]erformant +pgvector +Postgres +repo +Roboflow +[Ss]crollable +SQLAlchemy +Supabase +[Uu]psert +