From 312e9fdb34a4fbbe8280750be29b52c7f10e96e0 Mon Sep 17 00:00:00 2001 From: Michael Ong Date: Fri, 22 Aug 2025 16:15:07 +0800 Subject: [PATCH 01/15] chore: add additional playwright tests for table editor (#37972) --- .../footer/pagination/Pagination.tsx | 2 + apps/studio/components/ui/GridFooter.tsx | 1 + e2e/studio/features/database.spec.ts | 65 +- .../files/table-editor-import-file.csv | 51 ++ .../files/table-editor-import-paste.txt | 52 ++ e2e/studio/features/sql-editor.spec.ts | 6 +- e2e/studio/features/table-editor.spec.ts | 787 +++++++++++------- e2e/studio/playwright.config.ts | 3 +- e2e/studio/utils/reset-local-storage.ts | 12 + e2e/studio/utils/wait-for-response.ts | 19 + 10 files changed, 628 insertions(+), 370 deletions(-) create mode 100644 e2e/studio/features/files/table-editor-import-file.csv create mode 100644 e2e/studio/features/files/table-editor-import-paste.txt create mode 100644 e2e/studio/utils/reset-local-storage.ts diff --git a/apps/studio/components/grid/components/footer/pagination/Pagination.tsx b/apps/studio/components/grid/components/footer/pagination/Pagination.tsx index ed6472e9ba0..8c0ff3af657 100644 --- a/apps/studio/components/grid/components/footer/pagination/Pagination.tsx +++ b/apps/studio/components/grid/components/footer/pagination/Pagination.tsx @@ -149,6 +149,7 @@ const Pagination = () => { <>
+ {authenticationSignInProviders && ( + + )}
{isActive ? (
diff --git a/apps/studio/components/interfaces/Docs/GeneralContent.tsx b/apps/studio/components/interfaces/Docs/GeneralContent.tsx index da63210858d..0550dfc1c42 100644 --- a/apps/studio/components/interfaces/Docs/GeneralContent.tsx +++ b/apps/studio/components/interfaces/Docs/GeneralContent.tsx @@ -2,7 +2,7 @@ import Authentication from 'components/interfaces/Docs/Authentication' import Introduction from 'components/interfaces/Docs/Introduction' import RpcIntroduction from 'components/interfaces/Docs/Pages/Rpc/Introduction' import TablesIntroduction from 'components/interfaces/Docs/Pages/Tables/Introduction' -import UserManagement from 'components/interfaces/Docs/Pages/UserManagement' +import { UserManagement } from 'components/interfaces/Docs/Pages/UserManagement' interface GeneralContentProps { page?: string diff --git a/apps/studio/components/interfaces/Docs/Pages/UserManagement.tsx b/apps/studio/components/interfaces/Docs/Pages/UserManagement.tsx index 036acd17192..ff4ef5661eb 100644 --- a/apps/studio/components/interfaces/Docs/Pages/UserManagement.tsx +++ b/apps/studio/components/interfaces/Docs/Pages/UserManagement.tsx @@ -3,6 +3,7 @@ import { useRouter } from 'next/router' import { useParams } from 'common' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { makeRandomString } from 'lib/helpers' import CodeSnippet from '../CodeSnippet' import Snippets from '../Snippets' @@ -14,11 +15,15 @@ interface UserManagementProps { showApiKey: string } -export default function UserManagement({ selectedLang, showApiKey }: UserManagementProps) { +export const UserManagement = ({ selectedLang, showApiKey }: UserManagementProps) => { const router = useRouter() const { ref: projectRef } = useParams() const keyToShow = showApiKey ? showApiKey : 'SUPABASE_KEY' + const { authenticationSignInProviders } = useIsFeatureEnabled([ + 'authentication:sign_in_providers', + ]) + const { data: settings } = useProjectSettingsV2Query({ projectRef }) const protocol = settings?.app_config?.protocol ?? 'https' const hostEndpoint = settings?.app_config?.endpoint ?? '' @@ -157,72 +162,80 @@ export default function UserManagement({ selectedLang, showApiKey }: UserManagem
-

Log in with Third Party OAuth

-
-
-

- Users can log in with Third Party OAuth like Google, Facebook, GitHub, and more. You - must first enable each of these in the Auth Providers settings{' '} - - - here - - {' '} - . -

-

- View all the available{' '} - - Third Party OAuth providers - -

-

- After they have logged in, all interactions using the Supabase JS client will be - performed as "that user". -

-

- Generate your Client ID and secret from:{` `} - - Google - - ,{` `} - - GitHub - - ,{` `} - - GitLab - - ,{` `} - - Facebook - - ,{` `} - - Bitbucket - - . -

-
-
- -
-
+ {authenticationSignInProviders && ( + <> +

Log in with Third Party OAuth

+
+
+

+ Users can log in with Third Party OAuth like Google, Facebook, GitHub, and more. You + must first enable each of these in the Auth Providers settings{' '} + + + here + + {' '} + . +

+

+ View all the available{' '} + + Third Party OAuth providers + +

+

+ After they have logged in, all interactions using the Supabase JS client will be + performed as "that user". +

+

+ Generate your Client ID and secret from:{` `} + + Google + + ,{` `} + + GitHub + + ,{` `} + + GitLab + + ,{` `} + + Facebook + + ,{` `} + + Bitbucket + + . +

+
+
+ +
+
+ + )}

User

diff --git a/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx b/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx index d2779272463..438cbd5fd70 100644 --- a/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx +++ b/apps/studio/components/interfaces/Home/NewProjectPanel/APIKeys.tsx @@ -42,7 +42,10 @@ export const APIKeys = () => { ]) const availableLanguages = [ - ...(javascriptExampleEnabled ? [{ name: 'Javascript', key: 'js' }] : []), + { + name: javascriptExampleEnabled ? 'Javascript' : 'Typescript', + key: 'js', + }, ...(dartExampleEnabled ? [{ name: 'Dart', key: 'dart' }] : []), ] const [selectedLanguage, setSelectedLanguage] = useState(availableLanguages[0]) diff --git a/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 0f2283b1b97..0874e31d8a2 100644 --- a/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/apps/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -6,6 +6,7 @@ import type { IntegrationProjectConnection } from 'data/integrations/integration import { ProjectIndexPageLink } from 'data/prefetchers/project.$ref' import type { ProjectInfo } from 'data/projects/projects-query' import type { ResourceWarning } from 'data/usage/resource-warnings-query' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from 'lib/constants' import InlineSVG from 'react-inlinesvg' import { inferProjectStatus } from './ProjectCard.utils' @@ -29,6 +30,10 @@ const ProjectCard = ({ const { name, ref: projectRef } = project const desc = `${project.cloud_provider} | ${project.region}` + const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ + 'project_homepage:show_instance_size', + ]) + const isBranchingEnabled = project.preview_branch_refs?.length > 0 const isGithubIntegrated = githubIntegration !== undefined const isVercelIntegrated = vercelIntegration !== undefined @@ -45,7 +50,9 @@ const ProjectCard = ({

{name}

{desc}
- {project.status !== 'INACTIVE' && } + {project.status !== 'INACTIVE' && projectHomepageShowInstanceSize && ( + + )} {isVercelIntegrated && (
{ - const showStripeWrapper = useIsFeatureEnabled('integrations:show_stripe_wrapper') + const { integrationsShowStripeWrapper } = useIsFeatureEnabled([ + 'integrations:show_stripe_wrapper', + ]) const [selectedCategory, setSelectedCategory] = useQueryState( 'category', @@ -41,7 +43,7 @@ export const AvailableIntegrations = () => { const installedIds = installedIntegrations.map((i) => i.id) // available integrations for install - const availableIntegrations = showStripeWrapper + const availableIntegrations = integrationsShowStripeWrapper ? allIntegrations : allIntegrations.filter((x) => x.id !== 'stripe_wrapper') const integrationsByCategory = diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx index 7ad2f1fb211..a14c87464c1 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/InstanceNode.tsx @@ -15,6 +15,7 @@ import { } from 'data/read-replicas/replicas-status-query' import { formatDatabaseID } from 'data/read-replicas/replicas.utils' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from 'lib/constants' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' import { @@ -44,7 +45,7 @@ interface NodeData { id: string provider: string region: Region - computeSize: string + computeSize?: string status: string inserted_at: string } @@ -109,6 +110,10 @@ export const PrimaryNode = ({ data }: NodeProps) => { // [Joshen] Just FYI Handles cannot be conditionally rendered const { provider, region, computeSize, numReplicas, numRegions, hasLoadBalancer } = data + const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ + 'project_homepage:show_instance_size', + ]) + return ( <> ) => {

{provider} - • - {computeSize} + {projectHomepageShowInstanceSize && ( + <> + • + {computeSize} + + )}

@@ -186,6 +195,10 @@ export const ReplicaNode = ({ data }: NodeProps) => { PermissionAction.CREATE, 'projects' ) + const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ + 'project_homepage:show_instance_size', + ]) + const [, setShowConnect] = useQueryState('showConnect', parseAsBoolean.withDefault(false)) const { data: databaseStatuses } = useReadReplicasStatusesQuery({ projectRef: ref }) @@ -289,7 +302,7 @@ export const ReplicaNode = ({ data }: NodeProps) => {

{region.name}

{provider} - {!!computeSize && ( + {projectHomepageShowInstanceSize && !!computeSize && ( <> • {computeSize} diff --git a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx index f96afaf94b5..6140367095b 100644 --- a/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx +++ b/apps/studio/components/interfaces/Settings/Infrastructure/InfrastructureConfiguration/MapView.tsx @@ -20,6 +20,7 @@ import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import { Database, useReadReplicasQuery } from 'data/read-replicas/replicas-query' import { formatDatabaseID } from 'data/read-replicas/replicas.utils' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from 'lib/constants' import type { AWS_REGIONS_KEYS } from 'shared-data' import { useDatabaseSelectorStateSnapshot } from 'state/database-selector' @@ -51,6 +52,9 @@ const MapView = ({ }: MapViewProps) => { const { ref } = useParams() const dbSelectorState = useDatabaseSelectorStateSnapshot() + const { projectHomepageShowInstanceSize } = useIsFeatureEnabled([ + 'project_homepage:show_instance_size', + ]) const [mount, setMount] = useState(false) const [zoom, setZoom] = useState(1.5) @@ -286,7 +290,9 @@ const MapView = ({ Unhealthy )}

-

AWS • {database.size}

+

+ AWS{projectHomepageShowInstanceSize ? ` • ${database.size}` : ''} +

{database.identifier !== ref && (

Created on: {created}

)} diff --git a/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx b/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx index f67aef39e04..ff2e816a347 100644 --- a/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx @@ -58,6 +58,7 @@ const LogsQueryPanel = ({ onDateChange, }: LogsQueryPanelProps) => { const [showReference, setShowReference] = useState(false) + const { logsTemplates } = useIsFeatureEnabled(['logs:templates']) const { projectAuthAll: authEnabled, @@ -125,7 +126,7 @@ const LogsQueryPanel = ({ - {IS_PLATFORM && ( + {IS_PLATFORM && logsTemplates && (
- ) - } - > - {isLoading ? ( -
- -

Retrieving API keys

-
- ) : !canReadAPIKeys ? ( -
- + +
Project API Keys

- You don't have permission to view API keys. These keys restricted to users with higher - access levels. + Your API is secured behind an API gateway which requires an API Key for every request. +
+ You can use the keys below in the Supabase client libraries. +

- ) : isProjectSettingsError || isJwtSecretUpdateStatusError ? ( -
- -

- {isProjectSettingsError - ? 'Failed to retrieve API keys' - : 'Failed to update JWT secret'} -

-
- ) : isApiKeysEmpty || isProjectSettingsLoading || isJwtSecretUpdateStatusLoading ? ( -
- -

- {isProjectSettingsLoading || isApiKeysEmpty - ? 'Retrieving API keys' - : 'JWT secret is being updated'} -

-
- ) : ( - apiKeys.map((x, i: number) => ( - = 1 && - 'border-t border-panel-border-interior-light [[data-theme*=dark]_&]:border-panel-border-interior-dark' - } - > - - {x.tags?.split(',').map((x, i: number) => ( - - {x} + ) + } + > + {isLoading ? ( +
+ +

Retrieving API keys

+
+ ) : !canReadAPIKeys ? ( +
+ +

+ You don't have permission to view API keys. These keys restricted to users with higher + access levels. +

+
+ ) : isProjectSettingsError || isJwtSecretUpdateStatusError ? ( +
+ +

+ {isProjectSettingsError ? 'Failed to retrieve API keys' : 'Failed to update JWT secret'} +

+
+ ) : isApiKeysEmpty || isProjectSettingsLoading || isJwtSecretUpdateStatusLoading ? ( +
+ +

+ {isProjectSettingsLoading || isApiKeysEmpty + ? 'Retrieving API keys' + : 'JWT secret is being updated'} +

+
+ ) : ( + apiKeys.map((x, i: number) => ( + = 1 && + 'border-t border-panel-border-interior-light [[data-theme*=dark]_&]:border-panel-border-interior-dark' + } + > + + {x.tags?.split(',').map((x, i: number) => ( + + {x} + + ))} + {x.tags === 'service_role' && ( + <> + + secret - ))} - {x.tags === 'service_role' && ( - <> - - secret - - + + )} + {x.tags === 'anon' && public} + + } + copy={canReadAPIKeys && isNotUpdatingJwtSecret} + reveal={x.tags !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret} + value={ + !canReadAPIKeys + ? 'You need additional permissions to view API keys' + : jwtSecretUpdateStatus === JwtSecretUpdateStatus.Failed + ? 'JWT secret update failed, new API key may have issues' + : jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updating + ? 'Updating JWT secret...' + : x?.api_key ?? 'You need additional permissions to view API keys' + } + onChange={() => {}} + descriptionText={ + x.tags === 'service_role' ? ( + <> + This key has the ability to bypass Row Level Security. Never share it publicly. + If leaked, generate a new JWT secret immediately.{' '} + {showLegacyText && ( + + Prefer using{' '} + + Secret API keys + {' '} + instead. + )} - {x.tags === 'anon' && public} - } - copy={canReadAPIKeys && isNotUpdatingJwtSecret} - reveal={x.tags !== 'anon' && canReadAPIKeys && isNotUpdatingJwtSecret} - value={ - !canReadAPIKeys - ? 'You need additional permissions to view API keys' - : jwtSecretUpdateStatus === JwtSecretUpdateStatus.Failed - ? 'JWT secret update failed, new API key may have issues' - : jwtSecretUpdateStatus === JwtSecretUpdateStatus.Updating - ? 'Updating JWT secret...' - : x?.api_key ?? 'You need additional permissions to view API keys' - } - onChange={() => {}} - descriptionText={ - x.tags === 'service_role' ? ( - <> - This key has the ability to bypass Row Level Security. Never share it - publicly. If leaked, generate a new JWT secret immediately.{' '} - {showLegacyText && ( - - Prefer using{' '} - - Secret API keys - {' '} - instead. - - )} - - ) : ( - <> - This key is safe to use in a browser if you have enabled Row Level Security - for your tables and configured policies.{' '} - {showLegacyText && ( - - Prefer using{' '} - - Publishable API keys - {' '} - instead. - - )} - - ) - } - /> - -
- {lastUsedAPIKeys[x.api_key] - ? `Last request was ${lastUsedAPIKeys[x.api_key]} ago.` - : 'No requests in the past 24 hours.'} -
-
- )) - )} - + ) : ( + <> + This key is safe to use in a browser if you have enabled Row Level Security for + your tables and configured policies.{' '} + {showLegacyText && ( + + Prefer using{' '} + + Publishable API keys + {' '} + instead. + + )} + + ) + } + /> +
+ {lastUsedAPIKeys[x.api_key] + ? `Last request was ${lastUsedAPIKeys[x.api_key]} ago.` + : 'No requests in the past 24 hours.'} +
+
+ )) + )} {showNotice ? ( - ) : projectSettingsShowDisableLegacyApiKeys ? ( - ) : null} - + ) } diff --git a/apps/studio/pages/project/[ref]/settings/api-keys/index.tsx b/apps/studio/pages/project/[ref]/settings/api-keys/index.tsx index 89db964ba92..adb2080fe26 100644 --- a/apps/studio/pages/project/[ref]/settings/api-keys/index.tsx +++ b/apps/studio/pages/project/[ref]/settings/api-keys/index.tsx @@ -2,10 +2,21 @@ import ApiKeysLayout from 'components/layouts/APIKeys/APIKeysLayout' import DefaultLayout from 'components/layouts/DefaultLayout' import SettingsLayout from 'components/layouts/ProjectSettingsLayout/SettingsLayout' import { DisplayApiSettings } from 'components/ui/ProjectSettings' +import { ToggleLegacyApiKeysPanel } from 'components/ui/ProjectSettings/ToggleLegacyApiKeys' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { NextPageWithLayout } from 'types' const ApiKeysLegacyPage: NextPageWithLayout = () => { - return + const { projectSettingsShowDisableLegacyApiKeys } = useIsFeatureEnabled([ + 'project_settings:show_disable_legacy_api_keys', + ]) + + return ( + <> + + {projectSettingsShowDisableLegacyApiKeys && } + + ) } ApiKeysLegacyPage.getLayout = (page) => ( diff --git a/packages/common/enabled-features/enabled-features.schema.json b/packages/common/enabled-features/enabled-features.schema.json index f75bab7b4b5..0e979da780c 100644 --- a/packages/common/enabled-features/enabled-features.schema.json +++ b/packages/common/enabled-features/enabled-features.schema.json @@ -132,7 +132,7 @@ "project_connection:javascript_example": { "type": "boolean", - "description": "Show the javascript example in the project connection settings" + "description": "Show the javascript example in the project connection settings. If off, it'll show up as Typescript example instead (both are the same code example)" }, "project_connection:dart_example": { "type": "boolean", @@ -146,7 +146,7 @@ "project_homepage:show_instance_size": { "type": "boolean", - "description": "Show the instance size badge in the project homepage" + "description": "Show the instance size badge in the project homepage and infrastructure settings" }, "project_homepage:show_examples": { "type": "boolean", From 159ede242f3d80b5dc73da7d1f382868f2c6eabe Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Fri, 22 Aug 2025 10:19:17 +0200 Subject: [PATCH 03/15] feat: add sign-in-partner page (#37699) * feat: add sign-in-partner page * Update sign in partner page --------- Co-authored-by: Joshen Lim --- .../interfaces/SignIn/SignInPartner.tsx | 54 +++++++++++++++++++ .../SignInLayout/ForgotPasswordLayout.tsx | 14 +++-- apps/studio/pages/reset-password.tsx | 10 ++-- apps/studio/pages/sign-in-fly-tos.tsx | 6 +-- apps/studio/pages/sign-in-partner.tsx | 24 +++++++++ 5 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 apps/studio/components/interfaces/SignIn/SignInPartner.tsx create mode 100644 apps/studio/pages/sign-in-partner.tsx diff --git a/apps/studio/components/interfaces/SignIn/SignInPartner.tsx b/apps/studio/components/interfaces/SignIn/SignInPartner.tsx new file mode 100644 index 00000000000..f7364b0fcb4 --- /dev/null +++ b/apps/studio/components/interfaces/SignIn/SignInPartner.tsx @@ -0,0 +1,54 @@ +import { InlineLink } from 'components/ui/InlineLink' +import { auth } from 'lib/gotrue' +import { Loader2 } from 'lucide-react' +import { useRouter } from 'next/router' +import { useEffect } from 'react' + +export const SignInPartner = () => { + const router = useRouter() + + useEffect(() => { + ;(async () => { + const params = new URLSearchParams(window.location.hash.substring(1)) + + const partner = params.get('partner') + const token = params.get('id_token') + + const { data } = await auth.getSession() + + if (!data.session && partner && token) { + try { + await auth.signInWithIdToken({ provider: partner, token }) + } finally { + router.replace({ pathname: '/sign-in-mfa' }) + } + } else { + router.replace({ pathname: '/sign-in' }) + } + })() + }, []) + + return ( +
+ +

Signing in to Supabase Dashboard

+

+ By continuing, you agree to Supabase's{' '} + + Terms of Service + {' '} + and{' '} + + Privacy Policy + + . +

+
+ ) +} diff --git a/apps/studio/components/layouts/SignInLayout/ForgotPasswordLayout.tsx b/apps/studio/components/layouts/SignInLayout/ForgotPasswordLayout.tsx index 59a008b68b4..58c8b61309f 100644 --- a/apps/studio/components/layouts/SignInLayout/ForgotPasswordLayout.tsx +++ b/apps/studio/components/layouts/SignInLayout/ForgotPasswordLayout.tsx @@ -3,12 +3,14 @@ import { useTheme } from 'next-themes' import Image from 'next/legacy/image' import Link from 'next/link' import { PropsWithChildren } from 'react' +import { cn } from 'ui' type ForgotPasswordLayoutProps = { - heading: string - subheading: string + heading?: string + subheading?: string logoLinkToMarketingSite?: boolean showHeadings?: boolean + className?: string } const ForgotPasswordLayout = ({ @@ -16,12 +18,18 @@ const ForgotPasswordLayout = ({ subheading, logoLinkToMarketingSite = false, showHeadings = true, + className, children, }: PropsWithChildren) => { const { resolvedTheme } = useTheme() return ( -
+
{showExamples && ( -
+

Example projects

-
- - - App Frameworks - - Mobile Frameworks - - - -
- {EXAMPLE_PROJECTS.filter((project) => project.type === 'app') - .sort((a, b) => a.title.localeCompare(b.title)) - .map((project) => ( - - ))} -
-
- -
- {EXAMPLE_PROJECTS.filter((project) => project.type === 'mobile') - .sort((a, b) => a.title.localeCompare(b.title)) - .map((project) => ( - - ))} -
-
-
-
+ {!!projectHomepageExampleProjects ? ( +
+ {projectHomepageExampleProjects + .sort((a, b) => a.title.localeCompare(b.title)) + .map((project) => ( + + ))} +
+ ) : ( +
+ + + App Frameworks + + Mobile Frameworks + + + +
+ {EXAMPLE_PROJECTS.filter((project) => project.type === 'app') + .sort((a, b) => a.title.localeCompare(b.title)) + .map((project) => ( + + ))} +
+
+ +
+ {EXAMPLE_PROJECTS.filter((project) => project.type === 'mobile') + .sort((a, b) => a.title.localeCompare(b.title)) + .map((project) => ( + + ))} +
+
+
+
+ )}
)} From b8b01efae674435bf934cf1d913d3ad2ed46e8d1 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 22 Aug 2025 17:23:25 +0800 Subject: [PATCH 07/15] Chore/custom content logs default query (#38087) * Init custom content hook * Implement useCustomContent hook similarly to useIsFeatureEnabled, and implement extension of organization documents * Attempt to type things nicely * Add support for custom content example projects * Add support for custom content logs explorer default query * nit * Fix types --- .../hooks/custom-content/CustomContent.types.ts | 2 ++ apps/studio/hooks/custom-content/custom-content.json | 4 +++- .../hooks/custom-content/custom-content.sample.json | 4 +++- .../hooks/custom-content/custom-content.schema.json | 11 ++++++++++- .../pages/project/[ref]/logs/explorer/index.tsx | 8 ++++++-- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/studio/hooks/custom-content/CustomContent.types.ts b/apps/studio/hooks/custom-content/CustomContent.types.ts index 57bfadada57..94634447b3c 100644 --- a/apps/studio/hooks/custom-content/CustomContent.types.ts +++ b/apps/studio/hooks/custom-content/CustomContent.types.ts @@ -11,4 +11,6 @@ export type CustomContentTypes = { iconUrl: string url: string }[] + + logsDefaultQuery: string } diff --git a/apps/studio/hooks/custom-content/custom-content.json b/apps/studio/hooks/custom-content/custom-content.json index eb9035fde27..db8c9a471b8 100644 --- a/apps/studio/hooks/custom-content/custom-content.json +++ b/apps/studio/hooks/custom-content/custom-content.json @@ -3,5 +3,7 @@ "organization:legal_documents": null, - "project_homepage:example_projects": null + "project_homepage:example_projects": null, + + "logs:default_query": null } diff --git a/apps/studio/hooks/custom-content/custom-content.sample.json b/apps/studio/hooks/custom-content/custom-content.sample.json index b9454c592b4..4fd040f0cc0 100644 --- a/apps/studio/hooks/custom-content/custom-content.sample.json +++ b/apps/studio/hooks/custom-content/custom-content.sample.json @@ -35,5 +35,7 @@ "iconUrl": "https://supabase.com/dashboard/img/supabase-logo.svg", "url": "https://supabase.com" } - ] + ], + + "logs:default_query": "This is a sample query" } diff --git a/apps/studio/hooks/custom-content/custom-content.schema.json b/apps/studio/hooks/custom-content/custom-content.schema.json index 9d67dae109e..c3e5c5919f8 100644 --- a/apps/studio/hooks/custom-content/custom-content.schema.json +++ b/apps/studio/hooks/custom-content/custom-content.schema.json @@ -37,8 +37,17 @@ "url": { "type": "string" } } } + }, + + "logs:default_query": { + "type": ["string", "null"], + "description": "Renders a default query when landing on the logs explorer" } }, - "required": ["organization:legal_documents", "project_homepage:example_projects"], + "required": [ + "organization:legal_documents", + "project_homepage:example_projects", + "logs:default_query" + ], "additionalProperties": false } diff --git a/apps/studio/pages/project/[ref]/logs/explorer/index.tsx b/apps/studio/pages/project/[ref]/logs/explorer/index.tsx index a62ec23c3f1..79f7ff78c29 100644 --- a/apps/studio/pages/project/[ref]/logs/explorer/index.tsx +++ b/apps/studio/pages/project/[ref]/logs/explorer/index.tsx @@ -36,6 +36,7 @@ import { } from 'data/content/content-upsert-mutation' import useLogsQuery from 'hooks/analytics/useLogsQuery' import { useLogsUrlState } from 'hooks/analytics/useLogsUrlState' +import { useCustomContent } from 'hooks/custom-content/useCustomContent' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt' import { uuidv4 } from 'lib/helpers' @@ -57,8 +58,6 @@ const LOCAL_PLACEHOLDER_QUERY = const PLATFORM_PLACEHOLDER_QUERY = 'select\n cast(timestamp as datetime) as timestamp,\n event_message, metadata \nfrom edge_logs \nlimit 5' -const PLACEHOLDER_QUERY = IS_PLATFORM ? PLATFORM_PLACEHOLDER_QUERY : LOCAL_PLACEHOLDER_QUERY - export const LogsExplorerPage: NextPageWithLayout = () => { useEditorHints() const monaco = useMonaco() @@ -72,6 +71,11 @@ export const LogsExplorerPage: NextPageWithLayout = () => { const [editorId] = useState(uuidv4()) const { timestampStart, timestampEnd, setTimeRange } = useLogsUrlState() + const { logsDefaultQuery } = useCustomContent(['logs:default_query']) + const PLACEHOLDER_QUERY = IS_PLATFORM + ? logsDefaultQuery ?? PLATFORM_PLACEHOLDER_QUERY + : LOCAL_PLACEHOLDER_QUERY + const [editorValue, setEditorValue] = useState(PLACEHOLDER_QUERY) const [saveModalOpen, setSaveModalOpen] = useState(false) const [warnings, setWarnings] = useState([]) From f9027a29cdab21eb22f82337065faaf99ea420b6 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 22 Aug 2025 17:57:40 +0800 Subject: [PATCH 08/15] Chore/custom content connect UI frameworks (#38090) * Init custom content hook * Implement useCustomContent hook similarly to useIsFeatureEnabled, and implement extension of organization documents * Attempt to type things nicely * Add support for custom content example projects * Add support for custom content logs explorer default query * nit * Add support for custom content connect UI frameworks * Reset custom-content.json * Smol nit * set custom-content to null --------- Co-authored-by: Jordi Enric --- .../interfaces/Connect/Connect.constants.ts | 4 ++ .../components/interfaces/Connect/Connect.tsx | 45 +++++++++++---- .../interfaces/Connect/ConnectDropdown.tsx | 15 ++--- .../interfaces/Connect/ConnectTabContent.tsx | 4 +- .../Connect/ConnectTabContentCustom.tsx | 56 +++++++++++++++++++ .../interfaces/Connect/ConnectTabs.tsx | 10 +++- .../interfaces/Connect/ConnectionIcon.tsx | 25 +++++---- .../content/nextjs/app/supabasejs/content.tsx | 4 +- .../custom-content/CustomContent.types.ts | 17 ++++++ .../hooks/custom-content/custom-content.json | 4 +- .../custom-content/custom-content.sample.json | 37 +++++++++++- .../custom-content/custom-content.schema.json | 33 ++++++++++- 12 files changed, 212 insertions(+), 42 deletions(-) create mode 100644 apps/studio/components/interfaces/Connect/ConnectTabContentCustom.tsx diff --git a/apps/studio/components/interfaces/Connect/Connect.constants.ts b/apps/studio/components/interfaces/Connect/Connect.constants.ts index 926d2151ce2..80f82d9560a 100644 --- a/apps/studio/components/interfaces/Connect/Connect.constants.ts +++ b/apps/studio/components/interfaces/Connect/Connect.constants.ts @@ -64,6 +64,10 @@ export type ConnectionType = { label: string guideLink?: string children: ConnectionType[] + files?: { + name: string + content: string + }[] } export const FRAMEWORKS: ConnectionType[] = [ diff --git a/apps/studio/components/interfaces/Connect/Connect.tsx b/apps/studio/components/interfaces/Connect/Connect.tsx index 6d3065c670a..cbbcd64a943 100644 --- a/apps/studio/components/interfaces/Connect/Connect.tsx +++ b/apps/studio/components/interfaces/Connect/Connect.tsx @@ -9,6 +9,7 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip' import Panel from 'components/ui/Panel' import { getKeys, useAPIKeysQuery } from 'data/api-keys/api-keys-query' import { useProjectSettingsV2Query } from 'data/config/project-settings-v2-query' +import { useCustomContent } from 'hooks/custom-content/useCustomContent' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { PROJECT_STATUS } from 'lib/constants' @@ -30,20 +31,31 @@ import { } from 'ui' import { CONNECTION_TYPES, ConnectionType, FRAMEWORKS, MOBILES, ORMS } from './Connect.constants' import { getContentFilePath } from './Connect.utils' -import ConnectDropdown from './ConnectDropdown' -import ConnectTabContent from './ConnectTabContent' +import { ConnectDropdown } from './ConnectDropdown' +import { ConnectTabContent } from './ConnectTabContent' +import { ConnectTabContentCustom } from './ConnectTabContentCustom' export const Connect = () => { const { ref: projectRef } = useParams() const { data: selectedProject } = useSelectedProjectQuery() const isActiveHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY + const { connectFrameworks } = useCustomContent(['connect:frameworks']) + const connectionTypes = !connectFrameworks + ? CONNECTION_TYPES + : [ + { key: 'direct', label: 'Connection String', obj: [] }, + connectFrameworks, + { key: 'orms', label: 'ORMs', obj: ORMS }, + ] + const frameworks = !connectFrameworks ? FRAMEWORKS : connectFrameworks.obj + const [showConnect, setShowConnect] = useQueryState( 'showConnect', parseAsBoolean.withDefault(false) ) - const [connectionObject, setConnectionObject] = useState(FRAMEWORKS) + const [connectionObject, setConnectionObject] = useState(frameworks) const [selectedParent, setSelectedParent] = useState(connectionObject[0].key) // aka nextjs const [selectedChild, setSelectedChild] = useState( connectionObject.find((item) => item.key === selectedParent)?.children[0]?.key ?? '' @@ -54,6 +66,8 @@ export const Connect = () => { ?.children.find((child) => child.key === selectedChild)?.children[0]?.key || '' ) + const isFrameworkSelected = frameworks.some((x) => x.key === selectedParent) + const { data: settings } = useProjectSettingsV2Query({ projectRef }, { enabled: showConnect }) const { can: canReadAPIKeys } = useAsyncCheckProjectPermissions( PermissionAction.READ, @@ -109,8 +123,8 @@ export const Connect = () => { function handleConnectionType(type: string) { if (type === 'frameworks') { - setConnectionObject(FRAMEWORKS) - handleConnectionTypeChange(FRAMEWORKS) + setConnectionObject(frameworks) + handleConnectionTypeChange(frameworks) } if (type === 'mobiles') { @@ -207,14 +221,14 @@ export const Connect = () => { handleConnectionType(value)}> - {CONNECTION_TYPES.map((type) => ( + {connectionTypes.map((type) => ( {type.label} ))} - {CONNECTION_TYPES.map((type) => { + {connectionTypes.map((type) => { const hasChildOptions = (connectionObject.find((parent) => parent.key === selectedParent)?.children.length || 0) > 0 @@ -290,11 +304,18 @@ export const Connect = () => {

Add the following files below to your application

- + {!!connectFrameworks && isFrameworkSelected ? ( + x.key === selectedParent)} + /> + ) : ( + + )} void label: string - items: any[] + items: ConnectionType[] } -const ConnectDropdown = ({ +export const ConnectDropdown = ({ state, updateState, label, @@ -53,11 +54,7 @@ const ConnectDropdown = ({ iconRight={} >
- {selectedItem?.icon ? ( - - ) : ( - - )} + {selectedItem?.icon ? : } {selectedItem?.label}
@@ -79,7 +76,7 @@ const ConnectDropdown = ({ }} className="flex gap-2 items-center" > - {item.icon ? : } + {item.icon ? : } {item.label} ) } - -export default ConnectDropdown diff --git a/apps/studio/components/interfaces/Connect/ConnectTabContent.tsx b/apps/studio/components/interfaces/Connect/ConnectTabContent.tsx index 890ceb58df6..3667d93dd48 100644 --- a/apps/studio/components/interfaces/Connect/ConnectTabContent.tsx +++ b/apps/studio/components/interfaces/Connect/ConnectTabContent.tsx @@ -27,7 +27,7 @@ interface ConnectContentTabProps extends HTMLAttributes { } } -const ConnectTabContent = forwardRef( +export const ConnectTabContent = forwardRef( ({ projectKeys, filePath, ...props }, ref) => { const { ref: projectRef } = useParams() const { data: selectedOrg } = useSelectedOrganizationQuery() @@ -102,5 +102,3 @@ const ConnectTabContent = forwardRef( ) ConnectTabContent.displayName = 'ConnectTabContent' - -export default ConnectTabContent diff --git a/apps/studio/components/interfaces/Connect/ConnectTabContentCustom.tsx b/apps/studio/components/interfaces/Connect/ConnectTabContentCustom.tsx new file mode 100644 index 00000000000..c56c8e194d0 --- /dev/null +++ b/apps/studio/components/interfaces/Connect/ConnectTabContentCustom.tsx @@ -0,0 +1,56 @@ +import { useEffect, useState } from 'react' + +import { cn, SimpleCodeBlock } from 'ui' +import { ConnectionType } from './Connect.constants' +import { projectKeys } from './Connect.types' +import { + ConnectTabContent, + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, +} from './ConnectTabs' + +interface ConnectTabContentCustomProps { + projectKeys: projectKeys + framework?: ConnectionType +} + +export const ConnectTabContentCustom = ({ + projectKeys, + framework, +}: ConnectTabContentCustomProps) => { + const { files = [] } = framework ?? {} + + const [selectedTab, setSelectedTab] = useState() + + useEffect(() => { + if (framework?.files) setSelectedTab(framework.files[0].name) + }, [framework]) + + return ( +
+ + + {files.map((x) => ( + + ))} + + + {files.map((x) => { + const format = x.name.split('.')[1] ?? 'bash' + const content = x.content + .replaceAll('{{apiUrl}}', projectKeys.apiUrl ?? '') + .replaceAll('{{anonKey}}', projectKeys.anonKey ?? '') + .replaceAll('{{publishableKey}}', projectKeys.publishableKey ?? '') + return ( + + + {content} + + + ) + })} + +
+ ) +} diff --git a/apps/studio/components/interfaces/Connect/ConnectTabs.tsx b/apps/studio/components/interfaces/Connect/ConnectTabs.tsx index ec7b1b5a61f..1443c7abfbe 100644 --- a/apps/studio/components/interfaces/Connect/ConnectTabs.tsx +++ b/apps/studio/components/interfaces/Connect/ConnectTabs.tsx @@ -12,20 +12,26 @@ interface ConnectTabTriggersProps { interface ConnectFileTabProps { children: ReactNode[] + value?: string + onValueChange?: (value: string) => void } interface ConnectTabContentProps { children: ReactNode value: string } -const ConnectTabs = ({ children }: ConnectFileTabProps) => { +const ConnectTabs = ({ children, value, onValueChange }: ConnectFileTabProps) => { const firstChild = children[0] const defaultValue = isValidElement(firstChild) ? (firstChild.props as any)?.children[0]?.props?.value || '' : null - return {children} + return ( + + {children} + + ) } const ConnectTabTrigger = ({ value }: ConnectTabTriggerProps) => { diff --git a/apps/studio/components/interfaces/Connect/ConnectionIcon.tsx b/apps/studio/components/interfaces/Connect/ConnectionIcon.tsx index fd2452d2961..27e93521dc8 100644 --- a/apps/studio/components/interfaces/Connect/ConnectionIcon.tsx +++ b/apps/studio/components/interfaces/Connect/ConnectionIcon.tsx @@ -4,26 +4,29 @@ import Image from 'next/image' import { BASE_PATH } from 'lib/constants' interface ConnectionIconProps { - connection: any + icon: string } -export const ConnectionIcon = ({ connection }: ConnectionIconProps) => { +export const ConnectionIcon = ({ icon }: ConnectionIconProps) => { const { resolvedTheme } = useTheme() - const imageFolder = ['ionic-angular'].includes(connection) ? 'icons/frameworks' : 'libraries' + const imageFolder = ['ionic-angular'].includes(icon) ? 'icons/frameworks' : 'libraries' const imageExtension = imageFolder === 'icons/frameworks' ? '' : '-icon' - - return ( - {`${connection} diff --git a/apps/studio/components/interfaces/Connect/content/nextjs/app/supabasejs/content.tsx b/apps/studio/components/interfaces/Connect/content/nextjs/app/supabasejs/content.tsx index 8108b197f09..8c56900d9e4 100644 --- a/apps/studio/components/interfaces/Connect/content/nextjs/app/supabasejs/content.tsx +++ b/apps/studio/components/interfaces/Connect/content/nextjs/app/supabasejs/content.tsx @@ -1,10 +1,10 @@ import type { ContentFileProps } from 'components/interfaces/Connect/Connect.types' import { + ConnectTabContent, ConnectTabs, ConnectTabTrigger, ConnectTabTriggers, - ConnectTabContent, } from 'components/interfaces/Connect/ConnectTabs' import { SimpleCodeBlock } from 'ui' @@ -154,4 +154,6 @@ export const createClient = (request: NextRequest) => { ) } +// [Joshen] Used as a dynamic import +// eslint-disable-next-line no-restricted-exports export default ContentFile diff --git a/apps/studio/hooks/custom-content/CustomContent.types.ts b/apps/studio/hooks/custom-content/CustomContent.types.ts index 94634447b3c..66816dffa96 100644 --- a/apps/studio/hooks/custom-content/CustomContent.types.ts +++ b/apps/studio/hooks/custom-content/CustomContent.types.ts @@ -1,3 +1,5 @@ +import { ConnectionType } from 'components/interfaces/Connect/Connect.constants' + export type CustomContentTypes = { organizationLegalDocuments: { id: string @@ -5,6 +7,7 @@ export type CustomContentTypes = { description: string action: { text: string; url: string } }[] + projectHomepageExampleProjects: { title: string description: string @@ -13,4 +16,18 @@ export type CustomContentTypes = { }[] logsDefaultQuery: string + + /** + * When declaring files for each framework, there are 3 properties that can be dynamically rendered into the file content using handlebar notation: + * - {{apiUrl}}: The API URL of the project + * - {{anonKey}}: The anonymous key of the project (if still using legacy API keys) + * - {{publishableKey}}: The publishable API key of the project (if using new API keys) + * + * These could be helpful in rendering, for e.g an environment file like `.env` + */ + connectFrameworks: { + key: string + label: string + obj: ConnectionType[] + } } diff --git a/apps/studio/hooks/custom-content/custom-content.json b/apps/studio/hooks/custom-content/custom-content.json index db8c9a471b8..1378a1822a9 100644 --- a/apps/studio/hooks/custom-content/custom-content.json +++ b/apps/studio/hooks/custom-content/custom-content.json @@ -5,5 +5,7 @@ "project_homepage:example_projects": null, - "logs:default_query": null + "logs:default_query": null, + + "connect:frameworks": null } diff --git a/apps/studio/hooks/custom-content/custom-content.sample.json b/apps/studio/hooks/custom-content/custom-content.sample.json index 4fd040f0cc0..7992201514d 100644 --- a/apps/studio/hooks/custom-content/custom-content.sample.json +++ b/apps/studio/hooks/custom-content/custom-content.sample.json @@ -37,5 +37,40 @@ } ], - "logs:default_query": "This is a sample query" + "logs:default_query": "This is a sample query", + + "connect:frameworks": { + "key": "frameworks", + "label": "Frameworks", + "obj": [ + { + "key": "framework-1", + "label": "Framework 1", + "icon": "https://supabase.com/dashboard/img/supabase-logo.svg", + "guideLink": "https://supabase.com/docs", + "children": [], + "files": [ + { + "name": "sample_env", + "content": "NEXT_PUBLIC_SUPABASE_URL={{apiUrl}}\nNEXT_PUBLIC_SUPABASE_ANON_KEY={{anonKey}}" + }, + { + "name": "sample.ts", + "content": "import { createClient } from \"@supabase/supabase-js\";\n\nconst supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;\nconst supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\nexport const supabase = createClient(supabaseUrl, supabaseKey);" + } + ] + }, + { + "key": "framework-2", + "label": "Framework 2", + "icon": "https://supabase.com/dashboard/img/supabase-logo.svg", + "guideLink": "https://supabase.com/docs", + "children": [], + "files": [ + { "name": "file3.tsx", "content": "Content of File 3" }, + { "name": "file4.tsx", "content": "Content of File 4" } + ] + } + ] + } } diff --git a/apps/studio/hooks/custom-content/custom-content.schema.json b/apps/studio/hooks/custom-content/custom-content.schema.json index c3e5c5919f8..6d7020a3447 100644 --- a/apps/studio/hooks/custom-content/custom-content.schema.json +++ b/apps/studio/hooks/custom-content/custom-content.schema.json @@ -25,6 +25,7 @@ } } }, + "project_homepage:example_projects": { "type": ["array", "null"], "description": "Renders a provided set of example projects under the project's home page", @@ -42,12 +43,42 @@ "logs:default_query": { "type": ["string", "null"], "description": "Renders a default query when landing on the logs explorer" + }, + + "connect:frameworks": { + "type": ["object", "null"], + "description": "Replaces the frameworks tab in the Connect modal when a defined set of frameworks", + "properties": { + "key": { "type": "string" }, + "label": { "type": "string" }, + "obj": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { "type": "string" }, + "icon": { "type": "string" }, + "label": { "type": "string" }, + "guideLink": { "type": "string" }, + "children": { "type": "array" }, + "files": { + "type": "array", + "properties": { + "name": { "type": "string" }, + "content": { "type": "string" } + } + } + } + } + } + } } }, "required": [ "organization:legal_documents", "project_homepage:example_projects", - "logs:default_query" + "logs:default_query", + "connect:frameworks" ], "additionalProperties": false } From 29ee6a2992c346cc566abd7a626e28cfb14bb1bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Barroso?= Date: Fri, 22 Aug 2025 13:43:47 +0200 Subject: [PATCH 09/15] style: use GitHub's right product name (#38099) * style: use GitHub's right product name * fix: use correct kotlin provider --- DEVELOPERS.md | 2 +- .../content/guides/auth/native-mobile-deep-linking.mdx | 2 +- .../content/guides/auth/social-login/auth-github.mdx | 2 +- apps/docs/content/guides/functions.mdx | 2 +- ...anisation---lost-supabase-account-access-5wsE_1.mdx | 2 +- apps/docs/spec/supabase_kt_v2.yml | 2 +- apps/docs/spec/supabase_kt_v3.yml | 2 +- .../App/FeaturePreview/FeaturePreviewModal.tsx | 4 ++-- .../IntegrationSettings/IntegrationSettings.tsx | 2 +- .../interfaces/Organization/OAuthApps/OAuthApps.tsx | 2 +- .../interfaces/Support/LibrarySuggestions.tsx | 6 +++--- apps/studio/components/interfaces/Support/Success.tsx | 2 +- .../components/interfaces/Support/SupportFormV2.tsx | 2 +- .../components/layouts/AppLayout/BranchDropdown.tsx | 2 +- .../data/integrations/github-branch-check-query.ts | 2 +- .../integrations/github-connection-create-mutation.ts | 2 +- .../integrations/github-connection-delete-mutation.ts | 2 +- .../integrations/github-connection-update-mutation.ts | 2 +- apps/studio/instrumentation-client.ts | 2 +- apps/studio/pages/api/get-deployment-commit.ts | 2 +- .../app/example/social-auth/auth/login/page.tsx | 2 +- .../content/docs/react-router/social-auth.mdx | 2 +- apps/ui-library/content/docs/react/social-auth.mdx | 2 +- apps/ui-library/content/docs/tanstack/social-auth.mdx | 2 +- .../social-auth-nextjs/components/login-form.tsx | 2 +- .../social-auth-react-router/app/routes/login.tsx | 2 +- .../blocks/social-auth-react/components/login-form.tsx | 2 +- .../social-auth-tanstack/components/login-form.tsx | 2 +- .../_blog/2022-06-15-loading-data-supabase-python.mdx | 2 +- ...2022-06-30-flutter-tutorial-building-a-chat-app.mdx | 4 ++-- .../2022-08-03-supabase-beta-update-july-2022.mdx | 2 +- apps/www/_blog/2022-08-17-supabase-soc2.mdx | 4 ++-- ...realtime-trello-board-with-supabase-and-angular.mdx | 4 ++-- .../2022-10-05-supabase-beta-update-september-2022.mdx | 2 +- .../2022-11-08-authentication-in-ionic-angular.mdx | 6 +++--- .../_blog/2022-12-06-the-supabase-content-storm.mdx | 2 +- .../_blog/2023-01-05-supabase-beta-december-2022.mdx | 4 ++-- .../_blog/2023-02-08-supabase-beta-january-2023.mdx | 6 +++--- ...03-01-geo-queries-with-postgis-in-ionic-angular.mdx | 4 ++-- apps/www/_blog/2023-08-01-react-native-storage.mdx | 4 ++-- apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx | 2 +- .../_blog/2023-11-16-react-native-authentication.mdx | 2 +- .../2023-11-21-oauth2-login-python-flask-apps.mdx | 2 +- .../_blog/2023-12-11-studio-introducing-assistant.mdx | 4 ++-- apps/www/_blog/2024-04-18-s3-compatible-storage.mdx | 2 +- ...e-realtime-broadcast-and-presence-authorization.mdx | 2 +- apps/www/_blog/2024-08-15-log-drains.mdx | 2 +- apps/www/_blog/2024-09-30-lw12-hackathon-winners.mdx | 2 +- apps/www/_blog/2024-12-04-cli-v2-config-as-code.mdx | 2 +- apps/www/data/products/functions/page.tsx | 2 +- apps/www/lib/mdx/mdxSerialize.ts | 2 +- apps/www/pages/privacy-241009.mdx | 10 +++++----- apps/www/pages/privacy-250314.mdx | 10 +++++----- apps/www/pages/privacy.mdx | 10 +++++----- apps/www/public/rss.xml | 2 +- examples/realtime/nextjs-authorization-demo/README.md | 2 +- packages/common/telemetry-constants.ts | 2 +- 57 files changed, 83 insertions(+), 83 deletions(-) diff --git a/DEVELOPERS.md b/DEVELOPERS.md index c41044ceb71..ef6e298c9ae 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -203,7 +203,7 @@ Some things to consider: ## Community channels -If you get stuck somewhere or have any questions, join our [Discord Community Server](https://discord.supabase.com/) or the [Github Discussions](https://github.com/supabase/supabase/discussions). We are here to help! +If you get stuck somewhere or have any questions, join our [Discord Community Server](https://discord.supabase.com/) or the [GitHub Discussions](https://github.com/supabase/supabase/discussions). We are here to help! ## Contributors diff --git a/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx b/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx index 18d8d01a937..28058e5fe40 100644 --- a/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx +++ b/apps/docs/content/guides/auth/native-mobile-deep-linking.mdx @@ -102,7 +102,7 @@ With Deep Linking, you can configure this redirect to open a specific page. This return ( <> -
diff --git a/apps/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx b/apps/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx index bec757f81fa..88764d28498 100644 --- a/apps/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx +++ b/apps/studio/components/interfaces/Organization/IntegrationSettings/IntegrationSettings.tsx @@ -66,7 +66,7 @@ const IntegrationSettings = () => { const { mutate: deleteGitHubConnection } = useGitHubConnectionDeleteMutation({ onSuccess: () => { - toast.success('Successfully deleted Github connection') + toast.success('Successfully deleted GitHub connection') }, }) diff --git a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx index c7772e6272f..79b4464e083 100644 --- a/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx +++ b/apps/studio/components/interfaces/Organization/OAuthApps/OAuthApps.tsx @@ -23,7 +23,7 @@ import { RevokeAppModal } from './RevokeAppModal' // [Joshen] Note on nav UX // Kang Ming mentioned that it might be better to split Published Apps and Authorized Apps into 2 separate tabs -// to prevent any confusion (case study: Github). Authorized apps could be in the "integrations" tab, but let's +// to prevent any confusion (case study: GitHub). Authorized apps could be in the "integrations" tab, but let's // check in again after we wrap up Vercel integration const OAuthApps = () => { diff --git a/apps/studio/components/interfaces/Support/LibrarySuggestions.tsx b/apps/studio/components/interfaces/Support/LibrarySuggestions.tsx index 8bdea1ac3f4..ec720d32fa9 100644 --- a/apps/studio/components/interfaces/Support/LibrarySuggestions.tsx +++ b/apps/studio/components/interfaces/Support/LibrarySuggestions.tsx @@ -17,7 +17,7 @@ export const LibrarySuggestions = ({ library }: LibrarySuggestionsProps) => {

- Found an issue or a bug? Try searching our Github issues or submit a new one. + Found an issue or a bug? Try searching our GitHub issues or submit a new one.

@@ -37,7 +37,7 @@ export const LibrarySuggestions = ({ library }: LibrarySuggestionsProps) => {
@@ -57,7 +57,7 @@ export const LibrarySuggestions = ({ library }: LibrarySuggestionsProps) => {
diff --git a/apps/studio/components/interfaces/Support/Success.tsx b/apps/studio/components/interfaces/Support/Success.tsx index 9d5a42daa50..ff8f5037a0e 100644 --- a/apps/studio/components/interfaces/Support/Success.tsx +++ b/apps/studio/components/interfaces/Support/Success.tsx @@ -79,7 +79,7 @@ const Success = ({ target="_blank" rel="noreferrer" > - Search on Github discussions + Search on GitHub discussions , ]} diff --git a/apps/studio/components/interfaces/Support/SupportFormV2.tsx b/apps/studio/components/interfaces/Support/SupportFormV2.tsx index 9ee139f7cd9..a194baa25e6 100644 --- a/apps/studio/components/interfaces/Support/SupportFormV2.tsx +++ b/apps/studio/components/interfaces/Support/SupportFormV2.tsx @@ -584,7 +584,7 @@ export const SupportFormV2 = ({ rel="noreferrer" className="flex items-center gap-x-1 underline hover:text-foreground transition" > - Github discussions + GitHub discussions for a quick answer diff --git a/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx b/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx index 070a80311ba..5f5adc6c0ed 100644 --- a/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx +++ b/apps/studio/components/layouts/AppLayout/BranchDropdown.tsx @@ -230,7 +230,7 @@ export const BranchDropdown = () => {

Branching feedback

-

Join Github Discussion

+

Join GitHub Discussion

diff --git a/apps/studio/data/integrations/github-branch-check-query.ts b/apps/studio/data/integrations/github-branch-check-query.ts index e6046666972..05f21499c67 100644 --- a/apps/studio/data/integrations/github-branch-check-query.ts +++ b/apps/studio/data/integrations/github-branch-check-query.ts @@ -48,7 +48,7 @@ export const useCheckGithubBranchValidity = ({ }, async onError(data, variables, context) { if (onError === undefined) { - toast.error(`Failed to check Github branch: ${data.message}`) + toast.error(`Failed to check GitHub branch: ${data.message}`) } else { onError(data, variables, context) } diff --git a/apps/studio/data/integrations/github-connection-create-mutation.ts b/apps/studio/data/integrations/github-connection-create-mutation.ts index 18b3f1b256c..f1801cba2db 100644 --- a/apps/studio/data/integrations/github-connection-create-mutation.ts +++ b/apps/studio/data/integrations/github-connection-create-mutation.ts @@ -44,7 +44,7 @@ export const useGitHubConnectionCreateMutation = ({ }, async onError(data, variables, context) { if (onError === undefined) { - toast.error(`Failed to create Github connection: ${data.message}`) + toast.error(`Failed to create GitHub connection: ${data.message}`) } else { onError(data, variables, context) } diff --git a/apps/studio/data/integrations/github-connection-delete-mutation.ts b/apps/studio/data/integrations/github-connection-delete-mutation.ts index a46cd699a25..ad451eb5741 100644 --- a/apps/studio/data/integrations/github-connection-delete-mutation.ts +++ b/apps/studio/data/integrations/github-connection-delete-mutation.ts @@ -44,7 +44,7 @@ export const useGitHubConnectionDeleteMutation = ({ }, async onError(data, variables, context) { if (onError === undefined) { - toast.error(`Failed to delete Github connection: ${data.message}`) + toast.error(`Failed to delete GitHub connection: ${data.message}`) } else { onError(data, variables, context) } diff --git a/apps/studio/data/integrations/github-connection-update-mutation.ts b/apps/studio/data/integrations/github-connection-update-mutation.ts index 8153f76cd08..d15462ea502 100644 --- a/apps/studio/data/integrations/github-connection-update-mutation.ts +++ b/apps/studio/data/integrations/github-connection-update-mutation.ts @@ -50,7 +50,7 @@ export const useGitHubConnectionUpdateMutation = ({ }, async onError(data, variables, context) { if (onError === undefined) { - toast.error(`Failed to update Github connection: ${data.message}`) + toast.error(`Failed to update GitHub connection: ${data.message}`) } else { onError(data, variables, context) } diff --git a/apps/studio/instrumentation-client.ts b/apps/studio/instrumentation-client.ts index 06eca90031d..1c07a5b3c93 100644 --- a/apps/studio/instrumentation-client.ts +++ b/apps/studio/instrumentation-client.ts @@ -112,7 +112,7 @@ Sentry.init({ // [Joshen] Seems to be from hcaptcha "undefined is not an object (evaluating 'n.chat.setReady')", "undefined is not an object (evaluating 'i.chat.setReady')", - // [Terry] When users paste in an embedded Github Gist + // [Terry] When users paste in an embedded GitHub Gist // Error thrown by `sql-formatter` lexer when given invalid input // Original format: new Error(`Parse error: Unexpected "${text}" at line ${line} column ${col}`) /^Parse error: Unexpected ".+" at line \d+ column \d+$/, diff --git a/apps/studio/pages/api/get-deployment-commit.ts b/apps/studio/pages/api/get-deployment-commit.ts index 5b7cde2c504..929c3fcf714 100644 --- a/apps/studio/pages/api/get-deployment-commit.ts +++ b/apps/studio/pages/api/get-deployment-commit.ts @@ -24,7 +24,7 @@ export default async function handler( req: NextApiRequest, res: NextApiResponse<{ commitSha: string; commitTime: string }> ) { - // Set cache control headers for 10 minutes so that we don't get banned by Github API + // Set cache control headers for 10 minutes so that we don't get banned by GitHub API res.setHeader('Cache-Control', 's-maxage=600') // Get the build commit SHA from Vercel environment variable diff --git a/apps/ui-library/app/example/social-auth/auth/login/page.tsx b/apps/ui-library/app/example/social-auth/auth/login/page.tsx index 1ccb3c723c7..8c46f5dcdc8 100644 --- a/apps/ui-library/app/example/social-auth/auth/login/page.tsx +++ b/apps/ui-library/app/example/social-auth/auth/login/page.tsx @@ -23,7 +23,7 @@ const SocialAuthDemo = () => {
{error &&

{error}

}
diff --git a/apps/ui-library/content/docs/react-router/social-auth.mdx b/apps/ui-library/content/docs/react-router/social-auth.mdx index 3767e4d7e63..3347dce2004 100644 --- a/apps/ui-library/content/docs/react-router/social-auth.mdx +++ b/apps/ui-library/content/docs/react-router/social-auth.mdx @@ -6,7 +6,7 @@ description: Social authentication block for React Router - The block is using Github provider by default, but can be easily switched by changing a single + The block is using GitHub provider by default, but can be easily switched by changing a single parameter. diff --git a/apps/ui-library/content/docs/react/social-auth.mdx b/apps/ui-library/content/docs/react/social-auth.mdx index 62d3747b839..8f60157a796 100644 --- a/apps/ui-library/content/docs/react/social-auth.mdx +++ b/apps/ui-library/content/docs/react/social-auth.mdx @@ -6,7 +6,7 @@ description: Social authentication block for React Single Page Applications - The block is using Github provider by default, but can be easily switched by changing a single + The block is using GitHub provider by default, but can be easily switched by changing a single parameter. diff --git a/apps/ui-library/content/docs/tanstack/social-auth.mdx b/apps/ui-library/content/docs/tanstack/social-auth.mdx index 94c98d39d18..d4d4f755fd9 100644 --- a/apps/ui-library/content/docs/tanstack/social-auth.mdx +++ b/apps/ui-library/content/docs/tanstack/social-auth.mdx @@ -6,7 +6,7 @@ description: Social authentication block for Tanstack Start - The block is using Github provider by default, but can be easily switched by changing a single + The block is using GitHub provider by default, but can be easily switched by changing a single parameter. diff --git a/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx index 1462970f0d0..ca6a76de612 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx @@ -49,7 +49,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/login.tsx b/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/login.tsx index 48f8f8aacb6..c8e47c7a1cb 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/login.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-react-router/app/routes/login.tsx @@ -51,7 +51,7 @@ export default function Login() {
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/social-auth-react/components/login-form.tsx b/apps/ui-library/registry/default/blocks/social-auth-react/components/login-form.tsx index 591a9557cf7..265c60ed455 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-react/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-react/components/login-form.tsx @@ -47,7 +47,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
{error &&

{error}

}
diff --git a/apps/ui-library/registry/default/blocks/social-auth-tanstack/components/login-form.tsx b/apps/ui-library/registry/default/blocks/social-auth-tanstack/components/login-form.tsx index 05c715c1451..23f667a6456 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-tanstack/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-tanstack/components/login-form.tsx @@ -47,7 +47,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe
{error &&

{error}

}
diff --git a/apps/www/_blog/2022-06-15-loading-data-supabase-python.mdx b/apps/www/_blog/2022-06-15-loading-data-supabase-python.mdx index 988c862bbd0..82e14501335 100644 --- a/apps/www/_blog/2022-06-15-loading-data-supabase-python.mdx +++ b/apps/www/_blog/2022-06-15-loading-data-supabase-python.mdx @@ -33,7 +33,7 @@ Before we dive in, let's look at some prerequisites you'll need: ## Loading data into Supabase using Python -Supabase is built for developers, and you can [get started for free](https://supabase.com/dashboard) using your existing Github account. Once your Supabase account is set up, you will access the Supabase dashboard. From here, go to All Project > New Project. +Supabase is built for developers, and you can [get started for free](https://supabase.com/dashboard) using your existing GitHub account. Once your Supabase account is set up, you will access the Supabase dashboard. From here, go to All Project > New Project. ![screen shot of supabase dashboard](/images/blog/python-1/1.png) diff --git a/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx b/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx index 921800a4188..c951211165d 100644 --- a/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx +++ b/apps/www/_blog/2022-06-30-flutter-tutorial-building-a-chat-app.mdx @@ -24,7 +24,7 @@ In order to build this chat app, we will need a database to store all of the cha This article also assumes that you have installed Flutter already on your machine. If not, you can go to the [official Flutter page](https://docs.flutter.dev/get-started/install) to start the installation. -You can find the complete version of this app in [this](https://github.com/supabase-community/flutter-chat) Github repository. +You can find the complete version of this app in [this](https://github.com/supabase-community/flutter-chat) GitHub repository. ## Overview of the final chat app @@ -74,7 +74,7 @@ Run `flutter pub get` to install the packages. Note that you will have to termin If you do not have a Supabase account yet, do not worry, you can [get started for free](https://supabase.com/dashboard/). -You will be prompted to sign in using your Github account with a big green button, so let's go ahead and press it. Proceed with the sign up process and once you are done, you will be taken to a list of projects. You can go ahead and create a new project by pressing the “New Project” button at the top. +You will be prompted to sign in using your GitHub account with a big green button, so let's go ahead and press it. Proceed with the sign up process and once you are done, you will be taken to a list of projects. You can go ahead and create a new project by pressing the “New Project” button at the top. ![Create new Supabase project](/images/blog/flutter-chat/create-new-supabase-project.png) diff --git a/apps/www/_blog/2022-08-03-supabase-beta-update-july-2022.mdx b/apps/www/_blog/2022-08-03-supabase-beta-update-july-2022.mdx index 3eca65d93fc..636a6d25858 100644 --- a/apps/www/_blog/2022-08-03-supabase-beta-update-july-2022.mdx +++ b/apps/www/_blog/2022-08-03-supabase-beta-update-july-2022.mdx @@ -32,7 +32,7 @@ Supabase Flutter SDK is getting a major update, with a big focus on developer ex ![Sveltekit auth helpers](/images/blog/2022-july/auth-helpers.jpg) -We've been hard at work making an Auth experience that is as smooth as possible for Supabase developers. The new Auth Helpers have a better experience for server-side rendering (SSR) environments, new Github structure, SvelteKit support, and more. [Read the blog](https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support). +We've been hard at work making an Auth experience that is as smooth as possible for Supabase developers. The new Auth Helpers have a better experience for server-side rendering (SSR) environments, new GitHub structure, SvelteKit support, and more. [Read the blog](https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support). ## pg_jsonschema – A Postgres extension for JSON validation diff --git a/apps/www/_blog/2022-08-17-supabase-soc2.mdx b/apps/www/_blog/2022-08-17-supabase-soc2.mdx index 24e8880a584..8be8b88e046 100644 --- a/apps/www/_blog/2022-08-17-supabase-soc2.mdx +++ b/apps/www/_blog/2022-08-17-supabase-soc2.mdx @@ -38,9 +38,9 @@ It is also much easier to become-and remain-SOC2 compliant when you institute th ## Choosing a compliance monitoring tool -There are a few tools that plug into your cloud providers, task management system, identity, and HR providers and make it easy to monitor, collect and submit evidence to auditors. We evaluated [Vanta](https://www.vanta.com/), [Drata](https://drata.com/), [Secureframe](https://secureframe.com/), and [Tugboat Logic](https://tugboatlogic.com/). Our stack is pretty standard (AWS, GCP, GSuite, and Github). All the tools we evaluated were integrated with these systems. +There are a few tools that plug into your cloud providers, task management system, identity, and HR providers and make it easy to monitor, collect and submit evidence to auditors. We evaluated [Vanta](https://www.vanta.com/), [Drata](https://drata.com/), [Secureframe](https://secureframe.com/), and [Tugboat Logic](https://tugboatlogic.com/). Our stack is pretty standard (AWS, GCP, GSuite, and GitHub). All the tools we evaluated were integrated with these systems. -Some of them had overly broad permissions when integrating with our vendors (read/write access to Github for example) which made them a no-go. Some came with a lightweight agent that we could use for Mobile Device Management (MDM). Other than that, the products are largely the same. +Some of them had overly broad permissions when integrating with our vendors (read/write access to GitHub for example) which made them a no-go. Some came with a lightweight agent that we could use for Mobile Device Management (MDM). Other than that, the products are largely the same. We chose Vanta, with Drata coming in a close second. Vanta supported more compliance standards (at that time) and is a fellow YC company (which sealed the deal). The gap between these tools is much closer now since all of them support the compliance standards we have our eyes on. diff --git a/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx b/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx index b7c1828f0f4..5a758c2b4c9 100644 --- a/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx +++ b/apps/www/_blog/2022-08-24-building-a-realtime-trello-board-with-supabase-and-angular.mdx @@ -40,7 +40,7 @@ Along our jurney we will:
Watch the video version of the tutorial. -Since there are quite some code snippets we need I've put together the [full source code on Github](https://github.com/saimon24/supabase-trello-angular) so you can easily run the project yourself! +Since there are quite some code snippets we need I've put together the [full source code on GitHub](https://github.com/saimon24/supabase-trello-angular) so you can easily run the project yourself! If you are not familiar with [Trello](https://trello.com/), it's a way to manage projects with different boards, lists and cards! @@ -1384,7 +1384,7 @@ With that final piece of code we are completely done with our Supabase Angular T Building projects with Supabase is awesome, and hopefully this real world clone example gave you insight into different areas that you need to think about. -You can [find the full code of this tutorial on Github](https://github.com/saimon24/supabase-trello-angular) where you just need to insert your own Supabase instance and then create the tables with the included SQL file. +You can [find the full code of this tutorial on GitHub](https://github.com/saimon24/supabase-trello-angular) where you just need to insert your own Supabase instance and then create the tables with the included SQL file. If you enjoyed the tutorial, you can [find many more tutorials on my YouTube channel](https://www.youtube.com/simongrimmdev_) where I help web developers build awesome mobile apps. diff --git a/apps/www/_blog/2022-10-05-supabase-beta-update-september-2022.mdx b/apps/www/_blog/2022-10-05-supabase-beta-update-september-2022.mdx index 4e0bf485db4..c2b5fd865a4 100644 --- a/apps/www/_blog/2022-10-05-supabase-beta-update-september-2022.mdx +++ b/apps/www/_blog/2022-10-05-supabase-beta-update-september-2022.mdx @@ -26,7 +26,7 @@ It's pretty rare for a company to stop feature development altogether, but lucki We saw a lot of progress across our Open Issues - closing over 250 issues and 50 Pull Requests. -![Kaizen Week - Github Chart](/images/blog/2022-september/kaizen.jpg) +![Kaizen Week - GitHub Chart](/images/blog/2022-september/kaizen.jpg) and we shipped a few things: diff --git a/apps/www/_blog/2022-11-08-authentication-in-ionic-angular.mdx b/apps/www/_blog/2022-11-08-authentication-in-ionic-angular.mdx index ada48a190ef..7eae723a2d6 100644 --- a/apps/www/_blog/2022-11-08-authentication-in-ionic-angular.mdx +++ b/apps/www/_blog/2022-11-08-authentication-in-ionic-angular.mdx @@ -33,7 +33,7 @@ If you are not yet familiar with Ionic you can check out the [Ionic Quickstart g However, most of the logic is Angular based and therefore applies to any Angular web project as well. -You can [find the full code of this tutorial on Github](https://github.com/saimon24/supa-chat) where you just need to insert your own Supabase instance and then create the tables with the included SQL file. +You can [find the full code of this tutorial on GitHub](https://github.com/saimon24/supa-chat) where you just need to insert your own Supabase instance and then create the tables with the included SQL file. But before we dive into the app, let's set up our Supabase project! @@ -1316,7 +1316,7 @@ ion-textarea { } ``` -You can [find the full code of this tutorial on Github](https://github.com/saimon24/supa-chat) including the file for that pattern so your page looks almost like WhatsApp! +You can [find the full code of this tutorial on GitHub](https://github.com/saimon24/supa-chat) including the file for that pattern so your page looks almost like WhatsApp! ![Ionic Supabase Chat Page](/images/blog/simmon-grim-ionic-angular-authentication/ionic-supabase-chat-page.png) @@ -1517,7 +1517,7 @@ This was the last missing piece, and now you even got seamless Supabase authenti We've come a long way and covered everything from setting up tables, to defining policies to protect data, and handling authentication in Ionic Angular applications. -You can [find the full code of this tutorial on Github](https://github.com/saimon24/supa-chat) where you just need to insert your own Supabase instance and then create the tables with the included SQL file, plus updating the authentication settings as we did in the tutorial +You can [find the full code of this tutorial on GitHub](https://github.com/saimon24/supa-chat) where you just need to insert your own Supabase instance and then create the tables with the included SQL file, plus updating the authentication settings as we did in the tutorial Although we can now use magic link auth, something probably even better fitting for native apps would be [phone auth with Twilio](https://supabase.com/docs/guides/auth/phone-login/twilio) that's also easily possible with Supabase - just like tons of other authentication providers! diff --git a/apps/www/_blog/2022-12-06-the-supabase-content-storm.mdx b/apps/www/_blog/2022-12-06-the-supabase-content-storm.mdx index f237331e0d0..c45a2e21d7b 100644 --- a/apps/www/_blog/2022-12-06-the-supabase-content-storm.mdx +++ b/apps/www/_blog/2022-12-06-the-supabase-content-storm.mdx @@ -24,7 +24,7 @@ More than 30 creators participated in creating content in different formats and - [A quick look at Supabase](https://twitter.com/Rorstro/status/1600047388852293632) by Ania Kubów. - [Live streaming: Supabase and Sveltekit](https://www.youtube.com/@JamesQQuick/streams) by James Q Quick. -- [Perform Database Migration Using Github Actions & Supabase CLI](https://www.youtube.com/watch?v=iCkdtXSeq7A) by Kunal Kushwaha. +- [Perform Database Migration Using GitHub Actions & Supabase CLI](https://www.youtube.com/watch?v=iCkdtXSeq7A) by Kunal Kushwaha. - [Supabase Authentication & Realtime Database with Flutter](https://www.youtube.com/watch?v=UTSSYPOd5A0) by Pooja Bhaumik. - [Why I Switched From AWS Cognito To Supabase The Week Before My Startup Launched](https://medium.com/@sleeplessfox/why-i-switched-from-aws-cognito-to-supabase-the-week-before-my-startup-launched-67fdc1eccb8c) by Danilo à Tellinghusen. - [Supabase Crash Course For Python Developers](https://www.youtube.com/watch?v=M6cfT2pqpSc) by Patrick Loeber. diff --git a/apps/www/_blog/2023-01-05-supabase-beta-december-2022.mdx b/apps/www/_blog/2023-01-05-supabase-beta-december-2022.mdx index 9a51a868019..4905d9db00f 100644 --- a/apps/www/_blog/2023-01-05-supabase-beta-december-2022.mdx +++ b/apps/www/_blog/2023-01-05-supabase-beta-december-2022.mdx @@ -93,7 +93,7 @@ Two key features to level up your projects are now available: Custom domains all It was great to see so many high-quality projects. The decision wasn't easy, but in the end, the panel of judges chose Depulso as the winner of the Best Overall Project. -Congratulations 👏 to [@\_iamkarthikeyan](https://twitter.com/_iamkarthikeyan) and [@SindhuMansi](https://twitter.com/SindhuMansi) who get each a Supabase Darkmode Keyboard and the $1500 Github sponsorship! +Congratulations 👏 to [@\_iamkarthikeyan](https://twitter.com/_iamkarthikeyan) and [@SindhuMansi](https://twitter.com/SindhuMansi) who get each a Supabase Darkmode Keyboard and the $1500 GitHub sponsorship! - [Full list of Winners](https://supabase.com/blog/launch-week-6-hackathon-winners) - [See all the submissions](https://www.madewithsupabase.com/launch-week-6) @@ -107,7 +107,7 @@ Congratulations 👏 to [@\_iamkarthikeyan](https://twitter.com/_iamkarthikeyan) - Build any Front-end like Admin Panel or CRM for your Supabase. [Article](https://blog.jetadmin.io/how-to-build-any-front-end-such-as-admin-panel-or-crm-for-your-supabase-in-minutes/) - Everything Svelte: a course with all you need to know to build a modern web application. [Course](https://www.everythingsvelte.com/) - Edgy Edge Functions: a look at a new Edge Function per week. [Youtube Playlist](https://www.youtube.com/playlist?list=PL5S4mPUpp4OulD3olUW8Eq1IYKpUbk5Ob) -- Pixels, an app that provides canvas to create pixel art. [App](https://pixels.refine.dev/) | [Github Repo](https://github.com/refinedev/refine/tree/next/examples/pixels) +- Pixels, an app that provides canvas to create pixel art. [App](https://pixels.refine.dev/) | [GitHub Repo](https://github.com/refinedev/refine/tree/next/examples/pixels) - Supabase Angular authentication with RxJS Observables. [Tutorial](https://dev.to/kylerummens/supabase-angular-authentication-with-rxjs-observables-3d0f) - Create Calendar Events in React With Google Calendar API and Supabase. [Video Tutorial](https://www.youtube.com/watch?v=tgcCl52EN84) - Going Serverless with Supabase. [Part 1](https://dev.to/davepar/going-serverless-with-supabase-103h) diff --git a/apps/www/_blog/2023-02-08-supabase-beta-january-2023.mdx b/apps/www/_blog/2023-02-08-supabase-beta-january-2023.mdx index e191167ee07..21638da1712 100644 --- a/apps/www/_blog/2023-02-08-supabase-beta-january-2023.mdx +++ b/apps/www/_blog/2023-02-08-supabase-beta-january-2023.mdx @@ -82,11 +82,11 @@ We teamed up with DigitalOcean to level up self-hosted Supabase. This guide show [Get started](https://docs.digitalocean.com/developer-center/hosting-supabase-on-digitalocean/). -## 45k Github stars (and counting!) +## 45k GitHub stars (and counting!) -![45k Github stars (and counting!)](/images/blog/2023-02-08-january-beta-update/github-45k.png) +![45k GitHub stars (and counting!)](/images/blog/2023-02-08-january-beta-update/github-45k.png) -We hit two big milestones: 45k stars and Supabase is one the top 200 most-popular repos on Github 💥 +We hit two big milestones: 45k stars and Supabase is one the top 200 most-popular repos on GitHub 💥 Massive thanks to the community for all the support 👁️⚡️👁️ diff --git a/apps/www/_blog/2023-03-01-geo-queries-with-postgis-in-ionic-angular.mdx b/apps/www/_blog/2023-03-01-geo-queries-with-postgis-in-ionic-angular.mdx index f8b6a3b7900..1dc074c032f 100644 --- a/apps/www/_blog/2023-03-01-geo-queries-with-postgis-in-ionic-angular.mdx +++ b/apps/www/_blog/2023-03-01-geo-queries-with-postgis-in-ionic-angular.mdx @@ -29,7 +29,7 @@ In this tutorial you will learn to: - Display a [Capacitor Google Map](https://capacitorjs.com/docs/apis/google-maps) with a marker - Upload files to Supabase Storage and use [image transformations](https://supabase.com/docs/guides/storage/serving/image-transformations) -Since there are quite some code snippets we need I've put together the [full source code on Github](https://github.com/saimon24/supabase-postgis-ionic-angular) so you can easily run the project yourself! +Since there are quite some code snippets we need I've put together the [full source code on GitHub](https://github.com/saimon24/supabase-postgis-ionic-angular) so you can easily run the project yourself! Ready for some action? @@ -1030,7 +1030,7 @@ I was fascinated by the power of this simple PostGIS extension that we enabled w Building apps based on geolocation data is a very common scenario, and with PostGIS we can build these applications easily on the back of a [Supabase](https://supabase.com/) database (and [auth](https://supabase.com/blog/authentication-in-ionic-angular) ), and storage, and so much more..) -You can [find the full code of this tutorial on Github](https://github.com/saimon24/supabase-postgis-ionic-angular) where you just need to insert your own Supabase instance. your Google Maps key and then create the tables with the included SQL file. +You can [find the full code of this tutorial on GitHub](https://github.com/saimon24/supabase-postgis-ionic-angular) where you just need to insert your own Supabase instance. your Google Maps key and then create the tables with the included SQL file. If you enjoyed the tutorial, you can [find many more tutorials and courses on Galaxies.dev](https://galaxies.dev) where I help modern web and mobile developers build epic apps 🚀 diff --git a/apps/www/_blog/2023-08-01-react-native-storage.mdx b/apps/www/_blog/2023-08-01-react-native-storage.mdx index 1a48680ae32..11d001b9d64 100644 --- a/apps/www/_blog/2023-08-01-react-native-storage.mdx +++ b/apps/www/_blog/2023-08-01-react-native-storage.mdx @@ -26,7 +26,7 @@ In this tutorial, you will learn to: - Work with Expo Router v2 and protected routes - Upload files to [Supabase Storage](https://supabase.com/docs/guides/storage) -You can also directly check out the [full source code on Github](https://github.com/saimon24/react-native-resumable-upload-supabase) so you can get started with Supabase fast! +You can also directly check out the [full source code on GitHub](https://github.com/saimon24/react-native-resumable-upload-supabase) so you can get started with Supabase fast! Before we get into the app, let's quickly [start a new Supabase project](https://supabase.com/dashboard). @@ -606,7 +606,7 @@ It's almost too easy to use Supabase Storage, and it's a great way to store file You now have a fully functional image gallery app with React Native and Supabase Storage including user authentication without writing a line of backend code! -You can [find the full code of this tutorial on Github](https://github.com/saimon24/react-native-resumable-upload-supabase) where you just need to insert your own Supabase URL and API key. +You can [find the full code of this tutorial on GitHub](https://github.com/saimon24/react-native-resumable-upload-supabase) where you just need to insert your own Supabase URL and API key. If you enjoyed the tutorial, you can [learn React Native on Galaxies.dev](https://galaxies.dev) where I help developers build awesome React Native apps. diff --git a/apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx b/apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx index 4299c3340e6..4437bb46a2d 100644 --- a/apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx +++ b/apps/www/_blog/2023-08-09-supabase-studio-3-0.mdx @@ -99,7 +99,7 @@ Along with these huge AI features, we also added a bunch of new improvements els For a while now, many Supabase users have been using [Zernonia's](https://github.com/zernonia) [Supabase Schema visualization tool](https://supabase-schema.vercel.app/). While this was an amazing tool, many users wanted to see something like this directly integrated into the Studio. -We opened an [Issue for it on Github](https://github.com/supabase/supabase/issues/15585) and within a day or two the wheels were in motion. After a couple of weeks, the feature was polished up and merged. It's inspiring to see the power of open source at work. This feature wasn't trivial, and to see community members take it from feature request to production in just a couple of weeks is mind-blowing. Unquestionably, we have one of the best open source communities out there. Huge thanks to [kamilogorek](https://github.com/kamilogorek) and [adiologydev](https://github.com/adiologydev) for their work on this! +We opened an [Issue for it on GitHub](https://github.com/supabase/supabase/issues/15585) and within a day or two the wheels were in motion. After a couple of weeks, the feature was polished up and merged. It's inspiring to see the power of open source at work. This feature wasn't trivial, and to see community members take it from feature request to production in just a couple of weeks is mind-blowing. Unquestionably, we have one of the best open source communities out there. Huge thanks to [kamilogorek](https://github.com/kamilogorek) and [adiologydev](https://github.com/adiologydev) for their work on this! Special thanks as well to [Zernonia](https://twitter.com/zernonia) for providing the inspiration for this great new feature! OSS FTW. diff --git a/apps/www/_blog/2023-11-16-react-native-authentication.mdx b/apps/www/_blog/2023-11-16-react-native-authentication.mdx index 54c9f5c89f7..5c5ab09644f 100644 --- a/apps/www/_blog/2023-11-16-react-native-authentication.mdx +++ b/apps/www/_blog/2023-11-16-react-native-authentication.mdx @@ -344,7 +344,7 @@ export default function Auth() { return ( <> -
From aea43f6c143fe0ee68acb698ced389978d385fbe Mon Sep 17 00:00:00 2001 From: Jordi Enric <37541088+jordienr@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:12:24 +0200 Subject: [PATCH 13/15] add missing onRouterTransitionStart (#37968) * add missing onRouterTransitionStart * Update apps/studio/instrumentation-client.ts --- apps/studio/instrumentation-client.ts | 3 +++ turbo.json | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/studio/instrumentation-client.ts b/apps/studio/instrumentation-client.ts index 1c07a5b3c93..e1543a0e150 100644 --- a/apps/studio/instrumentation-client.ts +++ b/apps/studio/instrumentation-client.ts @@ -152,3 +152,6 @@ function standardiseRouterUrl(url: string) { return finalUrl } + +// This export will instrument router navigations, and is only relevant if you enable tracing. +export const onRouterTransitionStart = Sentry.captureRouterTransitionStart diff --git a/turbo.json b/turbo.json index 0409cabc4c9..cefe8e664e6 100644 --- a/turbo.json +++ b/turbo.json @@ -62,6 +62,7 @@ "NEXT_PUBLIC_GOTRUE_URL", "NEXT_PUBLIC_VERCEL_BRANCH_URL", "NEXT_PUBLIC_GOOGLE_MAPS_KEY", + "NEXT_RUNTIME", "NODE_ENV", "SUPABASE_URL", // These envs are used in the packages From a8f6d85c495603483e87bc4c4584ef710ffac05f Mon Sep 17 00:00:00 2001 From: Taishi Date: Mon, 25 Aug 2025 05:51:29 -0400 Subject: [PATCH 14/15] Add message deletion functionality to AI Assistant (#37698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add delete chat message functionality * Implement message editing functionality in AIAssistant * add functionality to delete messages in AI Assistant chat in assistant state * remove duplicated `text-xs` class * add a 'group' class for improved styling and refining action button visibility on hover * Refine action button visibility in AIAssistantPanel to show only for user messages on hover, improving user experience. * remove `"` from the message to edit * Enhance editing message display in AIAssistantPanel by updating styling for improved visibility and user experience. * Refactor AIAssistantPanel state management by simplifying original message content initialization and clarifying comments for better code readability. * clean up action button rendering in AIAssistantPanel message component * adjust the chat message layout (space) * simplify the editing mode message * show the edit button even when user is already in the edit mode * Update AssistantChatForm styling for editing mode, positioning the editing message above the input field without changing the layout * use `ButtonTooltip` for edit button to show tooltip * simplify the condiiton to show the edit buton * add the delete action button * Refactor action button rendering in AIAssistantPanel to improve clarity and maintainability. Ensure buttons are only shown for user messages on hover, and simplify the conditional rendering logic. * Remove commented-out code for cleaner implementation. * improve unused onDeleteAfter prop and consolidating delete logic into a single `deleteMessageFromHere` function for improved clarity * abort streaming when a user deletes a message * delete unnecessary `originalMessageContent` state variable * delete `confirmEdit` and move its logic into `sendMessageToAssistant` * add `strokeWidth={1.5}` to `Pencil` icon * add `opacity-50` to any messages that are after the message being edited, and it cancels the edit when those messages are clicked * add a pulse animation to the user’s message being edited * add more space between mesages and `AssistantChatForm` * add framer-motion animation to `Editing message` element * update strokeWidth of Trash2 icon in Message component for better visibility * refactor Message component to make onDelete prop required * add `DeleteMessageConfirmModal` for message deletion confirmation in Message component * refactor message deletion logic in AIAssistant component to streamline editing process * cancel editing when a user clicks the edit button again or the edit button in subsequent messages * Add success toast notification for message deletion * add Pencil icon to AIAssistantPanel component and fixed duplicated tailwind class * Update AIAssistantPanel to use the Pencil icon * Refactor AIAssistant to set editing message content from text parts * Remove unused `reload` prop from AIAssistant component * `setIsSticky(false)` when a user deletes all the messages or clear the chat to remove the `ArrowDown` icon scroll down button * refactor: remove deleteMessagesBefore function from AiAssistantState * refactor: simplify AssistantChatForm layout by removing unnecessary `z-index` * enable the input field when a user wants to edit the message while the response is streaming * Smol fixes * remove conosle log * Add state for resubmitting in AI Assistant panel * Remove classes that hide the focus styles + fix focusing when editing message to ensure cursor is at the end of the text --------- Co-authored-by: Joshen Lim --- .../ui/AIAssistantPanel/AIAssistant.tsx | 72 +++++++++++++++---- .../ui/AIAssistantPanel/AssistantChatForm.tsx | 7 +- .../DeleteMessageConfirmModal.tsx | 54 ++++++++++++++ .../ui/AIAssistantPanel/Message.tsx | 69 ++++++++++++------ .../components/ui/AIAssistantPanel/hooks.ts | 2 +- 5 files changed, 168 insertions(+), 36 deletions(-) create mode 100644 apps/studio/components/ui/AIAssistantPanel/DeleteMessageConfirmModal.tsx diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx index ad98e71ee94..b189896edd7 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx @@ -42,6 +42,7 @@ const MemoizedMessage = memo( message, isLoading, onResults, + onDelete, onEdit, isAfterEditedMessage, isBeingEdited, @@ -58,6 +59,7 @@ const MemoizedMessage = memo( resultId?: string results: any[] }) => void + onDelete: (id: string) => void onEdit: (id: string) => void isAfterEditedMessage: boolean isBeingEdited: boolean @@ -70,6 +72,7 @@ const MemoizedMessage = memo( readOnly={message.role === 'user'} isLoading={isLoading} onResults={onResults} + onDelete={onDelete} onEdit={onEdit} isAfterEditedMessage={isAfterEditedMessage} isBeingEdited={isBeingEdited} @@ -105,7 +108,7 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { ) const inputRef = useRef(null) - const { ref: scrollContainerRef, isSticky, scrollToEnd } = useAutoScroll() + const { ref: scrollContainerRef, isSticky, scrollToEnd, setIsSticky } = useAutoScroll() const { aiOptInLevel, isHipaaProjectDisallowed } = useOrgAiOptInLevel() const showMetadataWarning = @@ -119,6 +122,7 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { const [value, setValue] = useState(snap.initialInput || '') const [isConfirmOptInModalOpen, setIsConfirmOptInModalOpen] = useState(false) const [editingMessageId, setEditingMessageId] = useState(null) + const [isResubmitting, setIsResubmitting] = useState(false) const { data: check, isSuccess } = useCheckOpenAIKeyQuery() const isApiKeySet = IS_PLATFORM || !!check?.hasKey @@ -232,7 +236,7 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { onFinish: handleChatFinish, }) - const isChatLoading = chatStatus === 'submitted' || chatStatus === 'streaming' + const isChatLoading = chatStatus === 'submitted' || chatStatus === 'streaming' || isResubmitting const updateMessage = useCallback( ({ @@ -249,6 +253,22 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { [snap] ) + const deleteMessageFromHere = useCallback( + (messageId: string) => { + // Find the message index in current chatMessages + const messageIndex = chatMessages.findIndex((msg) => msg.id === messageId) + if (messageIndex === -1) return + + if (isChatLoading) stop() + + snap.deleteMessagesAfter(messageId, { includeSelf: true }) + + const updatedMessages = chatMessages.slice(0, messageIndex) + setMessages(updatedMessages) + }, + [snap, setMessages, chatMessages, isChatLoading, stop] + ) + const editMessage = useCallback( (messageId: string) => { const messageIndex = chatMessages.findIndex((msg) => msg.id === messageId) @@ -266,9 +286,16 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { .join('') ?? '' setValue(textContent) - if (inputRef.current) { - inputRef.current.focus() - } + setTimeout(() => { + if (inputRef.current) { + inputRef?.current?.focus() + + // [Joshen] This is just to make the cursor go to the end of the text when focusing + const val = inputRef.current.value + inputRef.current.value = '' + inputRef.current.value = val + } + }, 100) }, [chatMessages, setValue] ) @@ -292,6 +319,7 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { message={message} isLoading={isChatLoading && message.id === chatMessages[chatMessages.length - 1].id} onResults={updateMessage} + onDelete={deleteMessageFromHere} onEdit={editMessage} isAfterEditedMessage={isAfterEditedMessage} isBeingEdited={isBeingEdited} @@ -299,7 +327,15 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { /> ) }), - [chatMessages, isChatLoading, updateMessage, editMessage, editingMessageId, cancelEdit] + [ + chatMessages, + isChatLoading, + updateMessage, + deleteMessageFromHere, + editMessage, + cancelEdit, + editingMessageId, + ] ) const hasMessages = chatMessages.length > 0 @@ -308,12 +344,9 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { const sendMessageToAssistant = (finalContent: string) => { if (editingMessageId) { // Handling when the user is in edit mode - const messageIndex = chatMessages.findIndex((msg) => msg.id === editingMessageId) - if (messageIndex === -1) return - - snap.deleteMessagesAfter(editingMessageId, { includeSelf: true }) - const updatedMessages = chatMessages.slice(0, messageIndex) - setMessages(updatedMessages) + // delete the message(s) from the chat just like the delete button + setIsResubmitting(true) + deleteMessageFromHere(editingMessageId) setEditingMessageId(null) } @@ -355,6 +388,14 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { setEditingMessageId(null) } + useEffect(() => { + // Keep "Thinking" visible while stopping and resubmitting during edit + // Only clear once the new response actually starts streaming (or errors) + if (isResubmitting && (chatStatus === 'streaming' || !!error)) { + setIsResubmitting(false) + } + }, [isResubmitting, chatStatus, error]) + // Update scroll behavior for new messages useEffect(() => { if (!isChatLoading) { @@ -381,6 +422,10 @@ export const AIAssistant = ({ className }: AIAssistantProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [snap.open, isInSQLEditor, snippetContent]) + useEffect(() => { + if (chatMessages.length === 0 && !isSticky) setIsSticky(true) + }, [chatMessages, isSticky, setIsSticky]) + return ( { 'z-20 [&>form>textarea]:text-base [&>form>textarea]:md:text-sm [&>form>textarea]:border-1 [&>form>textarea]:rounded-md [&>form>textarea]:!outline-none [&>form>textarea]:!ring-offset-0 [&>form>textarea]:!ring-0' )} loading={isChatLoading} - disabled={!isApiKeySet || disablePrompts || isChatLoading} + isEditing={!!editingMessageId} + disabled={!isApiKeySet || disablePrompts || (isChatLoading && !editingMessageId)} placeholder={ hasMessages ? 'Ask a follow up question...' diff --git a/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx b/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx index 58c954f9a09..edf949772a2 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AssistantChatForm.tsx @@ -41,6 +41,8 @@ export interface FormProps { snippetsClassName?: string /* Additional class name for the form wrapper */ className?: string + /* If currently editing an existing message */ + isEditing?: boolean } const AssistantChatFormComponent = forwardRef( @@ -59,6 +61,7 @@ const AssistantChatFormComponent = forwardRef( snippetsClassName, includeSnippetsInMessage = false, className, + isEditing = false, ...props }, ref @@ -68,7 +71,7 @@ const AssistantChatFormComponent = forwardRef( const handleSubmit = (event?: FormEvent) => { if (event) event.preventDefault() - if (!value || loading) return + if (!value || (loading && !isEditing)) return let finalMessage = value if (includeSnippetsInMessage && sqlSnippets && sqlSnippets.length > 0) { @@ -107,8 +110,8 @@ const AssistantChatFormComponent = forwardRef( /> )} void + onCancel: () => void +} + +export const DeleteMessageConfirmModal = ({ + visible, + onConfirm, + onCancel, +}: DeleteMessageConfirmModalProps) => { + const onOpenChange = (open: boolean) => { + if (!open) onCancel() + } + + return ( + + + + Delete Message + + + + + +

+ Are you sure you want to delete this message and all subsequent messages? This action + cannot be undone. +

+
+ + + + + +
+
+ ) +} diff --git a/apps/studio/components/ui/AIAssistantPanel/Message.tsx b/apps/studio/components/ui/AIAssistantPanel/Message.tsx index 3a295e2d6ce..c30bb9776a6 100644 --- a/apps/studio/components/ui/AIAssistantPanel/Message.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/Message.tsx @@ -1,9 +1,10 @@ import { UIMessage as VercelMessage } from '@ai-sdk/react' -import { Loader2, Pencil } from 'lucide-react' -import { createContext, PropsWithChildren, ReactNode, useMemo } from 'react' +import { Loader2, Pencil, Trash2 } from 'lucide-react' +import { createContext, PropsWithChildren, ReactNode, useMemo, useState } from 'react' import ReactMarkdown from 'react-markdown' import { Components } from 'react-markdown/lib/ast-to-react' import remarkGfm from 'remark-gfm' +import { toast } from 'sonner' import { ProfileImage } from 'components/ui/ProfileImage' import { useProfile } from 'lib/profile' @@ -11,6 +12,7 @@ import { cn, markdownComponents, WarningIcon } from 'ui' import { ButtonTooltip } from '../ButtonTooltip' import { EdgeFunctionBlock } from '../EdgeFunctionBlock/EdgeFunctionBlock' import { DisplayBlockRenderer } from './DisplayBlockRenderer' +import { DeleteMessageConfirmModal } from './DeleteMessageConfirmModal' import { Heading3, Hyperlink, @@ -51,6 +53,7 @@ interface MessageProps { resultId?: string results: any[] }) => void + onDelete: (id: string) => void onEdit: (id: string) => void isAfterEditedMessage: boolean isBeingEdited: boolean @@ -65,12 +68,14 @@ export const Message = function Message({ action = null, variant = 'default', onResults, + onDelete, onEdit, isAfterEditedMessage = false, isBeingEdited = false, onCancelEdit, }: PropsWithChildren) { const { profile } = useProfile() + const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false) const allMarkdownComponents: Partial = useMemo( () => ({ ...markdownComponents, @@ -237,30 +242,54 @@ export const Message = function Message({ Assistant is thinking... )} - {/* Action button - only show for user messages on hover */} -
+ {/* Action buttons - only show for user messages on hover */} +
{message.role === 'user' && ( - } - onClick={isBeingEdited || isAfterEditedMessage ? onCancelEdit : () => onEdit(id)} - className="text-foreground-light hover:text-foreground p-1 rounded" - aria-label={ - isBeingEdited || isAfterEditedMessage ? 'Cancel editing' : 'Edit message' - } - tooltip={{ - content: { - side: 'bottom', - text: - isBeingEdited || isAfterEditedMessage ? 'Cancel editing' : 'Edit message', - }, - }} - /> + <> + } + onClick={ + isBeingEdited || isAfterEditedMessage ? onCancelEdit : () => onEdit(id) + } + className="text-foreground-light hover:text-foreground p-1 rounded" + aria-label={ + isBeingEdited || isAfterEditedMessage ? 'Cancel editing' : 'Edit message' + } + tooltip={{ + content: { + side: 'bottom', + text: + isBeingEdited || isAfterEditedMessage ? 'Cancel editing' : 'Edit message', + }, + }} + /> + + } + tooltip={{ content: { side: 'bottom', text: 'Delete message' } }} + onClick={() => setShowDeleteConfirmModal(true)} + className="text-foreground-light hover:text-foreground p-1 rounded" + title="Delete message" + aria-label="Delete message" + /> + )}
+ + { + onDelete(id) + setShowDeleteConfirmModal(false) + toast.success('Message deleted successfully') + }} + onCancel={() => setShowDeleteConfirmModal(false)} + /> ) } diff --git a/apps/studio/components/ui/AIAssistantPanel/hooks.ts b/apps/studio/components/ui/AIAssistantPanel/hooks.ts index 507c0708e55..9d5c9b231ae 100644 --- a/apps/studio/components/ui/AIAssistantPanel/hooks.ts +++ b/apps/studio/components/ui/AIAssistantPanel/hooks.ts @@ -69,5 +69,5 @@ export function useAutoScroll({ enabled = true }: UseAutoScrollProps = {}) { } }, [container, enabled, scrollToEnd]) - return { ref, isSticky, scrollToEnd } + return { ref, isSticky, scrollToEnd, setIsSticky } } From 6c85144aa57271477377aaeb418994e27593838d Mon Sep 17 00:00:00 2001 From: Div Arora Date: Mon, 25 Aug 2025 17:56:56 +0800 Subject: [PATCH 15/15] chore: add nimbus staging to CSP (#38176) --- apps/studio/csp.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/studio/csp.js b/apps/studio/csp.js index 69108c1e06e..c184c789d86 100644 --- a/apps/studio/csp.js +++ b/apps/studio/csp.js @@ -31,6 +31,8 @@ const isDevOrStaging = process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' || process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging' +const NIMBUS_STAGING_PROJECTS_URL = 'https://*.supabase-nimbus-projects.com' +const NIMBUS_STAGING_PROJECTS_URL_WS = 'wss://*.supabase-nimbus-projects.com' const SUPABASE_STAGING_PROJECTS_URL = 'https://*.supabase.red' const SUPABASE_STAGING_PROJECTS_URL_WS = 'wss://*.supabase.red' const SUPABASE_COM_URL = 'https://supabase.com' @@ -126,6 +128,8 @@ module.exports.getCSP = function getCSP() { ? [ SUPABASE_STAGING_PROJECTS_URL, SUPABASE_STAGING_PROJECTS_URL_WS, + NIMBUS_STAGING_PROJECTS_URL, + NIMBUS_STAGING_PROJECTS_URL_WS, VERCEL_LIVE_URL, SUPABASE_DOCS_PROJECT_URL, SUPABASE_CONTENT_API_URL, @@ -140,7 +144,9 @@ module.exports.getCSP = function getCSP() { `blob:`, `data:`, ...IMG_SRC_URLS, - ...(isDevOrStaging ? [SUPABASE_STAGING_PROJECTS_URL, VERCEL_URL] : []), + ...(isDevOrStaging + ? [SUPABASE_STAGING_PROJECTS_URL, NIMBUS_STAGING_PROJECTS_URL, VERCEL_URL] + : []), ].join(' ') const scriptSrcDirective = [