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/Auth/Users/UserOverview.tsx b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx index 6a83f3c58fc..7737da47b31 100644 --- a/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx +++ b/apps/studio/components/interfaces/Auth/Users/UserOverview.tsx @@ -16,6 +16,7 @@ import { useUserSendOTPMutation } from 'data/auth/user-send-otp-mutation' import { useUserUpdateMutation } from 'data/auth/user-update-mutation' import { User } from 'data/auth/users-infinite-query' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { BASE_PATH } from 'lib/constants' import { timeout } from 'lib/helpers' import { Button, cn, Separator } from 'ui' @@ -45,6 +46,10 @@ export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => { const isPhoneAuth = user.phone !== null const isBanned = user.banned_until !== null + const { authenticationSignInProviders } = useIsFeatureEnabled([ + 'authentication:sign_in_providers', + ]) + const providers = ((user.raw_app_meta_data?.providers as string[]) ?? []).map( (provider: string) => { return { @@ -238,13 +243,15 @@ export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => { Signed in with a {providerName} account via{' '} {providerName === 'SAML' ? 'SSO' : 'OAuth'}

- + {authenticationSignInProviders && ( + + )} {isActive ? (
diff --git a/apps/studio/components/interfaces/Connect/Connect.constants.ts b/apps/studio/components/interfaces/Connect/Connect.constants.ts index 2752e2a315e..a7040297509 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/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/ExampleProject.tsx b/apps/studio/components/interfaces/Home/ExampleProject.tsx index 13a10c1a00b..8f6ba8c5736 100644 --- a/apps/studio/components/interfaces/Home/ExampleProject.tsx +++ b/apps/studio/components/interfaces/Home/ExampleProject.tsx @@ -6,20 +6,39 @@ import { useParams } from 'common' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { BASE_PATH } from 'lib/constants' +import { cn } from 'ui' interface ExampleProjectProps { - framework: string title: string description: string url: string + framework?: string + iconUrl?: string } -const ExampleProject = ({ framework, title, description, url }: ExampleProjectProps) => { +export const ExampleProject = ({ + framework, + title, + description, + url, + iconUrl, +}: ExampleProjectProps) => { const { resolvedTheme } = useTheme() const { ref: projectRef } = useParams() const { data: org } = useSelectedOrganizationQuery() const { mutate: sendEvent } = useSendEventMutation() + const iconImgSrc = iconUrl + ? iconUrl + : !!framework + ? `${BASE_PATH}/img/libraries/${framework.toLowerCase()}${ + ['expo', 'nextjs'].includes(framework.toLowerCase()) + ? resolvedTheme?.includes('dark') + ? '-dark' + : '' + : '' + }-icon.svg` + : '' return (
{`${framework}{description}

@@ -79,5 +87,3 @@ const ExampleProject = ({ framework, title, description, url }: ExampleProjectPr ) } - -export default ExampleProject 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/Integrations/Wrappers/WrapperRow.tsx b/apps/studio/components/interfaces/Integrations/Wrappers/WrapperRow.tsx index b79a47aa275..f64438c7d7d 100644 --- a/apps/studio/components/interfaces/Integrations/Wrappers/WrapperRow.tsx +++ b/apps/studio/components/interfaces/Integrations/Wrappers/WrapperRow.tsx @@ -8,7 +8,16 @@ import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import type { FDW } from 'data/fdw/fdws-query' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' -import { Badge, Sheet, SheetContent, TableCell, TableRow } from 'ui' +import { + Badge, + Sheet, + SheetContent, + TableCell, + TableRow, + Tooltip, + TooltipContent, + TooltipTrigger, +} from 'ui' import { INTEGRATIONS } from '../Landing/Integrations.constants' import DeleteWrapperModal from './DeleteWrapperModal' import { EditWrapperSheet } from './EditWrapperSheet' @@ -74,7 +83,12 @@ const WrapperRow = ({ wrapper }: WrapperRowProps) => {
{integration.icon({ className: 'p-0' })}
- {target}{' '} + + {target} + + {target} + + { - {table.schema}.{table.table_name} + + + {table.schema}.{table.table_name} + + + {table.schema}.{table.table_name} + +
diff --git a/apps/studio/components/interfaces/Organization/Documents/CustomDocument.tsx b/apps/studio/components/interfaces/Organization/Documents/CustomDocument.tsx new file mode 100644 index 00000000000..76c9b4ca7de --- /dev/null +++ b/apps/studio/components/interfaces/Organization/Documents/CustomDocument.tsx @@ -0,0 +1,33 @@ +import { + ScaffoldContainer, + ScaffoldSection, + ScaffoldSectionContent, + ScaffoldSectionDetail, +} from 'components/layouts/Scaffold' +import { CustomContentTypes } from 'hooks/custom-content/CustomContent.types' +import { ExternalLink } from 'lucide-react' +import { Button } from 'ui' + +interface CustomDocumentProps { + doc: CustomContentTypes['organizationLegalDocuments'][number] +} + +export const CustomDocument = ({ doc }: CustomDocumentProps) => { + return ( + + + +

{doc.name}

+

{doc.description}

+
+ + + +
+
+ ) +} diff --git a/apps/studio/components/interfaces/Organization/Documents/Documents.tsx b/apps/studio/components/interfaces/Organization/Documents/Documents.tsx index 84ecf7b32fe..1b35df888dc 100644 --- a/apps/studio/components/interfaces/Organization/Documents/Documents.tsx +++ b/apps/studio/components/interfaces/Organization/Documents/Documents.tsx @@ -1,6 +1,9 @@ import Link from 'next/link' import { ScaffoldContainer, ScaffoldDivider, ScaffoldSection } from 'components/layouts/Scaffold' +import { useCustomContent } from 'hooks/custom-content/useCustomContent' +import { Fragment } from 'react' +import { CustomDocument } from './CustomDocument' import { DPA } from './DPA' import { HIPAA } from './HIPAA' import { SecurityQuestionnaire } from './SecurityQuestionnaire' @@ -8,6 +11,19 @@ import { SOC2 } from './SOC2' import { TIA } from './TIA' const Documents = () => { + const { organizationLegalDocuments } = useCustomContent(['organization:legal_documents']) + + if (Array.isArray(organizationLegalDocuments)) { + return organizationLegalDocuments.map((doc, idx) => { + return ( + + + {idx !== organizationLegalDocuments.length - 1 && } + + ) + }) + } + 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/Organization/Usage/Compute.tsx b/apps/studio/components/interfaces/Organization/Usage/Compute.tsx index 4436a14590d..e32c1458b89 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Compute.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Compute.tsx @@ -8,6 +8,7 @@ import { DataPoint } from 'data/analytics/constants' import { useOrgDailyComputeStatsQuery } from 'data/analytics/org-daily-compute-stats-query' import { ComputeUsageMetric, computeUsageMetricLabel } from 'data/analytics/org-daily-stats-query' import type { OrgSubscription } from 'data/subscriptions/types' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import SectionContent from './SectionContent' import { Attribute, AttributeColor } from './Usage.constants' import UsageBarChart from './UsageBarChart' @@ -34,6 +35,8 @@ const Compute = ({ orgSlug, projectRef, startDate, endDate }: ComputeProps) => { endDate, }) + const { billingAll } = useIsFeatureEnabled(['billing:all']) + const chartData: DataPoint[] = egressData?.data ?? [] const COMPUTE_TO_COLOR: Record = { @@ -79,16 +82,18 @@ const Compute = ({ orgSlug, projectRef, startDate, endDate }: ComputeProps) => { name: 'Compute Hours', description: 'Amount of hours your projects were active. Each project is a dedicated server and database.\nPaid plans come with $10 in Compute Credits to cover one project running on Micro Compute or parts of any compute add-on.\nBilling is based on the sum of Compute Hours used. Paused projects do not count towards usage.', - links: [ - { - name: 'Compute Add-ons', - url: 'https://supabase.com/docs/guides/platform/compute-add-ons', - }, - { - name: 'Usage-billing for Compute', - url: 'https://supabase.com/docs/guides/platform/manage-your-usage/compute', - }, - ], + links: billingAll + ? [ + { + name: 'Compute Add-ons', + url: 'https://supabase.com/docs/guides/platform/compute-add-ons', + }, + { + name: 'Usage-billing for Compute', + url: 'https://supabase.com/docs/guides/platform/manage-your-usage/compute', + }, + ] + : [], }} > {isLoading && } diff --git a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx index 726ec20f6ff..c6329db4280 100644 --- a/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/TotalUsage.tsx @@ -9,6 +9,7 @@ import { } from 'data/analytics/org-daily-stats-query' import type { OrgSubscription } from 'data/subscriptions/types' import { useOrgUsageQuery } from 'data/usage/org-usage-query' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { cn } from 'ui' import { BILLING_BREAKDOWN_METRICS } from '../BillingSettings/BillingBreakdown/BillingBreakdown.constants' import BillingMetric from '../BillingSettings/BillingBreakdown/BillingMetric' @@ -32,7 +33,7 @@ const METRICS_TO_HIDE_WITH_NO_USAGE: PricingMetric[] = [ PricingMetric.DISK_THROUGHPUT_GP3, ] -const TotalUsage = ({ +export const TotalUsage = ({ orgSlug, projectRef, subscription, @@ -41,6 +42,7 @@ const TotalUsage = ({ currentBillingCycleSelected, }: ComputeProps) => { const isUsageBillingEnabled = subscription?.usage_billing_enabled + const { billingAll } = useIsFeatureEnabled(['billing:all']) const { data: usage, @@ -117,16 +119,18 @@ const TotalUsage = ({ description: isUsageBillingEnabled ? `Your plan includes a limited amount of usage. If exceeded, you will be charged for the overages. It may take up to 1 hour to refresh.` : `Your plan includes a limited amount of usage. If exceeded, you may experience restrictions, as you are currently not billed for overages. It may take up to 1 hour to refresh.`, - links: [ - { - name: 'How billing works', - url: 'https://supabase.com/docs/guides/platform/billing-on-supabase', - }, - { - name: 'Supabase Plans', - url: 'https://supabase.com/pricing', - }, - ], + links: billingAll + ? [ + { + name: 'How billing works', + url: 'https://supabase.com/docs/guides/platform/billing-on-supabase', + }, + { + name: 'Supabase Plans', + url: 'https://supabase.com/pricing', + }, + ] + : [], }} > {isLoadingUsage && ( @@ -234,5 +238,3 @@ const TotalUsage = ({
) } - -export default TotalUsage diff --git a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx index 80a68493edf..32420143d36 100644 --- a/apps/studio/components/interfaces/Organization/Usage/Usage.tsx +++ b/apps/studio/components/interfaces/Organization/Usage/Usage.tsx @@ -22,10 +22,10 @@ import { cn, Listbox } from 'ui' import { Admonition } from 'ui-patterns' import { Restriction } from '../BillingSettings/Restriction' import Activity from './Activity' -import Egress from './Egress' import Compute from './Compute' +import Egress from './Egress' import SizeAndCounts from './SizeAndCounts' -import TotalUsage from './TotalUsage' +import { TotalUsage } from './TotalUsage' const Usage = () => { const { slug, projectRef } = useParams() diff --git a/apps/studio/components/interfaces/ProjectAPIDocs/Content/UserManagement.tsx b/apps/studio/components/interfaces/ProjectAPIDocs/Content/UserManagement.tsx index 22e762d9eb5..e4bc2e18795 100644 --- a/apps/studio/components/interfaces/ProjectAPIDocs/Content/UserManagement.tsx +++ b/apps/studio/components/interfaces/ProjectAPIDocs/Content/UserManagement.tsx @@ -1,8 +1,12 @@ +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import ContentSnippet from '../ContentSnippet' import { DOCS_CONTENT } from '../ProjectAPIDocs.constants' import type { ContentProps } from './Content.types' const UserManagement = ({ language, apikey, endpoint }: ContentProps) => { + const { authenticationSignInProviders } = useIsFeatureEnabled([ + 'authentication:sign_in_providers', + ]) return ( <> @@ -42,12 +46,14 @@ const UserManagement = ({ language, apikey, endpoint }: ContentProps) => { endpoint={endpoint} snippet={DOCS_CONTENT.smsVerify} /> - + {authenticationSignInProviders && ( + + )} ) => { // [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 && ( @@ -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/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx index eb37a0bb682..9c813d61bb2 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx @@ -187,7 +187,12 @@ const ColumnType = ({ - + Type not found. 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/components/layouts/AuthLayout/Auth.Commands.tsx b/apps/studio/components/layouts/AuthLayout/Auth.Commands.tsx index d7fefeb0cb3..a062de0b9d0 100644 --- a/apps/studio/components/layouts/AuthLayout/Auth.Commands.tsx +++ b/apps/studio/components/layouts/AuthLayout/Auth.Commands.tsx @@ -1,15 +1,35 @@ import { Lock } from 'lucide-react' +import { useParams } from 'common' import { COMMAND_MENU_SECTIONS } from 'components/interfaces/App/CommandMenu/CommandMenu.utils' +import { orderCommandSectionsByPriority } from 'components/interfaces/App/CommandMenu/ordering' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { CommandOptions } from 'ui-patterns/CommandMenu' import { useRegisterCommands } from 'ui-patterns/CommandMenu' -import { orderCommandSectionsByPriority } from 'components/interfaces/App/CommandMenu/ordering' -import { useParams } from 'common' +import { IRouteCommand } from 'ui-patterns/CommandMenu/internal/types' export function useAuthGotoCommands(options?: CommandOptions) { let { ref } = useParams() ref ||= '_' + const { + authenticationSignInProviders, + authenticationThirdPartyAuth, + authenticationRateLimits, + authenticationEmails, + authenticationMultiFactor, + authenticationAttackProtection, + authenticationAdvanced, + } = useIsFeatureEnabled([ + 'authentication:sign_in_providers', + 'authentication:third_party_auth', + 'authentication:rate_limits', + 'authentication:emails', + 'authentication:multi_factor', + 'authentication:attack_protection', + 'authentication:advanced', + ]) + useRegisterCommands( 'Actions', [ @@ -47,20 +67,28 @@ export function useAuthGotoCommands(options?: CommandOptions) { route: `/project/${ref}/auth/policies`, defaultHidden: true, }, - { - id: 'nav-auth-providers', - name: 'Providers', - value: 'Auth: Providers (Social Login, SSO)', - route: `/project/${ref}/auth/providers`, - defaultHidden: true, - }, - { - id: 'nav-auth-providers', - name: 'Providers (Third Party)', - value: 'Auth: Providers (Third Party)', - route: `/project/${ref}/auth/third-party`, - defaultHidden: true, - }, + ...(authenticationSignInProviders + ? [ + { + id: 'nav-auth-providers', + name: 'Providers', + value: 'Auth: Providers (Social Login, SSO)', + route: `/project/${ref}/auth/providers`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), + ...(authenticationThirdPartyAuth + ? [ + { + id: 'nav-auth-providers', + name: 'Providers (Third Party)', + value: 'Auth: Providers (Third Party)', + route: `/project/${ref}/auth/third-party`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), { id: 'nav-auth-sessions', name: 'Sessions', @@ -68,27 +96,39 @@ export function useAuthGotoCommands(options?: CommandOptions) { route: `/project/${ref}/auth/sessions`, defaultHidden: true, }, - { - id: 'nav-auth-rate-limits', - name: 'Rate Limits', - value: 'Auth: Rate Limits', - route: `/project/${ref}/auth/rate-limits`, - defaultHidden: true, - }, - { - id: 'nav-auth-templates', - name: 'Email Templates', - value: 'Auth: Email Templates', - route: `/project/${ref}/auth/templates`, - defaultHidden: true, - }, - { - id: 'nav-auth-mfa', - name: 'Multi Factor Authentication (MFA)', - value: 'Auth: Multi Factor Authenticaiton (MFA)', - route: `/project/${ref}/auth/mfa`, - defaultHidden: true, - }, + ...(authenticationRateLimits + ? [ + { + id: 'nav-auth-rate-limits', + name: 'Rate Limits', + value: 'Auth: Rate Limits', + route: `/project/${ref}/auth/rate-limits`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), + ...(authenticationEmails + ? [ + { + id: 'nav-auth-templates', + name: 'Email Templates', + value: 'Auth: Email Templates', + route: `/project/${ref}/auth/templates`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), + ...(authenticationMultiFactor + ? [ + { + id: 'nav-auth-mfa', + name: 'Multi Factor Authentication (MFA)', + value: 'Auth: Multi Factor Authenticaiton (MFA)', + route: `/project/${ref}/auth/mfa`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), { id: 'nav-auth-url-configuration', name: 'URL Configuration', @@ -96,13 +136,17 @@ export function useAuthGotoCommands(options?: CommandOptions) { route: `/project/${ref}/auth/url-configuration`, defaultHidden: true, }, - { - id: 'nav-auth-attack-protection', - name: 'Attack Protection', - value: 'Auth: Attack Protection', - route: `/project/${ref}/auth/protection`, - defaultHidden: true, - }, + ...(authenticationAttackProtection + ? [ + { + id: 'nav-auth-attack-protection', + name: 'Attack Protection', + value: 'Auth: Attack Protection', + route: `/project/${ref}/auth/protection`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), { id: 'nav-auth-auth-hooks', name: 'Auth Hooks', @@ -110,13 +154,17 @@ export function useAuthGotoCommands(options?: CommandOptions) { route: `/project/${ref}/auth/hooks`, defaultHidden: true, }, - { - id: 'nav-auth-advanced-settings', - name: 'Auth Advanced Settings', - value: 'Auth: Advanced Settings', - route: `/project/${ref}/auth/advanced`, - defaultHidden: true, - }, + ...(authenticationAdvanced + ? [ + { + id: 'nav-auth-advanced-settings', + name: 'Auth Advanced Settings', + value: 'Auth: Advanced Settings', + route: `/project/${ref}/auth/advanced`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), ], { ...options, deps: [ref] } ) diff --git a/apps/studio/components/layouts/DatabaseLayout/Database.Commands.tsx b/apps/studio/components/layouts/DatabaseLayout/Database.Commands.tsx index ba42375641b..3abd34af48c 100644 --- a/apps/studio/components/layouts/DatabaseLayout/Database.Commands.tsx +++ b/apps/studio/components/layouts/DatabaseLayout/Database.Commands.tsx @@ -1,15 +1,22 @@ import { Blocks, Code, Database, History, Search } from 'lucide-react' +import { useParams } from 'common' import { COMMAND_MENU_SECTIONS } from 'components/interfaces/App/CommandMenu/CommandMenu.utils' +import { orderCommandSectionsByPriority } from 'components/interfaces/App/CommandMenu/ordering' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { CommandOptions } from 'ui-patterns/CommandMenu' import { useRegisterCommands } from 'ui-patterns/CommandMenu' -import { useParams } from 'common' -import { orderCommandSectionsByPriority } from 'components/interfaces/App/CommandMenu/ordering' +import { IRouteCommand } from 'ui-patterns/CommandMenu/internal/types' export function useDatabaseGotoCommands(options?: CommandOptions) { let { ref } = useParams() ref ||= '_' + const { databaseReplication, databaseRoles } = useIsFeatureEnabled([ + 'database:replication', + 'database:roles', + ]) + useRegisterCommands( COMMAND_MENU_SECTIONS.QUERY, [ @@ -59,20 +66,28 @@ export function useDatabaseGotoCommands(options?: CommandOptions) { route: `/project/${ref}/database/extensions`, defaultHidden: true, }, - { - id: 'nav-database-roles', - name: 'Roles', - value: 'Database: Roles', - route: `/project/${ref}/database/roles`, - defaultHidden: true, - }, - { - id: 'nav-database-replication', - name: 'Replication', - value: 'Database: Replication', - route: `/project/${ref}/database/replication`, - defaultHidden: true, - }, + ...(databaseRoles + ? [ + { + id: 'nav-database-roles', + name: 'Roles', + value: 'Database: Roles', + route: `/project/${ref}/database/roles`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), + ...(databaseReplication + ? [ + { + id: 'nav-database-replication', + name: 'Replication', + value: 'Database: Replication', + route: `/project/${ref}/database/replication`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), { id: 'nav-database-hooks', name: 'Webhooks', @@ -144,12 +159,16 @@ export function useDatabaseGotoCommands(options?: CommandOptions) { route: `/project/${ref}/database/indexes`, icon: () => , }, - { - id: 'run-view-database-roles', - name: 'View your roles', - route: `/project/${ref}/database/roles`, - icon: () => , - }, + ...(databaseRoles + ? [ + { + id: 'run-view-database-roles', + name: 'View your roles', + route: `/project/${ref}/database/roles`, + icon: () => , + } as IRouteCommand, + ] + : []), { id: 'run-view-database-backups', name: 'View your backups', diff --git a/apps/studio/components/layouts/IntegrationsLayout/Integrations.Commands.tsx b/apps/studio/components/layouts/IntegrationsLayout/Integrations.Commands.tsx index 0651deedf84..510407e9c72 100644 --- a/apps/studio/components/layouts/IntegrationsLayout/Integrations.Commands.tsx +++ b/apps/studio/components/layouts/IntegrationsLayout/Integrations.Commands.tsx @@ -5,6 +5,7 @@ import { IntegrationDefinition, INTEGRATIONS, } from 'components/interfaces/Integrations/Landing/Integrations.constants' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { CommandOptions } from 'ui-patterns/CommandMenu' import { useRegisterCommands } from 'ui-patterns/CommandMenu' @@ -12,6 +13,14 @@ export function useIntegrationsGotoCommands(options?: CommandOptions) { let { ref } = useParams() ref ||= '_' + const { integrationsShowStripeWrapper } = useIsFeatureEnabled([ + 'integrations:show_stripe_wrapper', + ]) + + const allIntegrations = integrationsShowStripeWrapper + ? INTEGRATIONS + : INTEGRATIONS.filter((x) => x.id !== 'stripe_wrapper') + const getName = (integration: IntegrationDefinition) => { switch (integration.id) { case 'cron': @@ -27,7 +36,7 @@ export function useIntegrationsGotoCommands(options?: CommandOptions) { useRegisterCommands( COMMAND_MENU_SECTIONS.NAVIGATE, - INTEGRATIONS.map((x) => { + allIntegrations.map((x) => { return { id: `nav-integrations-${x.id}`, name: x.name, @@ -41,7 +50,7 @@ export function useIntegrationsGotoCommands(options?: CommandOptions) { useRegisterCommands( COMMAND_MENU_SECTIONS.INTEGRATIONS, - INTEGRATIONS.map((x) => { + allIntegrations.map((x) => { return { id: `manage-${x.id}`, name: getName(x), diff --git a/apps/studio/components/layouts/LogsLayout/Logs.Commands.tsx b/apps/studio/components/layouts/LogsLayout/Logs.Commands.tsx index e302bb87400..d6fd444e8da 100644 --- a/apps/studio/components/layouts/LogsLayout/Logs.Commands.tsx +++ b/apps/studio/components/layouts/LogsLayout/Logs.Commands.tsx @@ -1,12 +1,16 @@ import { useParams } from 'common' import { COMMAND_MENU_SECTIONS } from 'components/interfaces/App/CommandMenu/CommandMenu.utils' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { CommandOptions } from 'ui-patterns/CommandMenu' import { useRegisterCommands } from 'ui-patterns/CommandMenu' +import { IRouteCommand } from 'ui-patterns/CommandMenu/internal/types' export function useLogsGotoCommands(options?: CommandOptions) { let { ref } = useParams() ref ||= '_' + const { logsCollections } = useIsFeatureEnabled(['logs:collections']) + useRegisterCommands( COMMAND_MENU_SECTIONS.NAVIGATE, [ @@ -16,42 +20,46 @@ export function useLogsGotoCommands(options?: CommandOptions) { route: `/project/${ref}/logs/explorer`, defaultHidden: true, }, - { - id: 'nav-logs-postgres', - name: 'Postgres Logs', - route: `/project/${ref}/logs/postgres-logs`, - defaultHidden: true, - }, - { - id: 'nav-logs-postgrest', - name: 'PostgREST Logs', - route: `/project/${ref}/logs/postgrest-logs`, - defaultHidden: true, - }, - { - id: 'nav-logs-pooler', - name: 'Pooler Logs', - route: `/project/${ref}/logs/pooler-logs`, - defaultHidden: true, - }, - { - id: 'nav-logs-auth', - name: 'Auth Logs', - route: `/project/${ref}/logs/auth-logs`, - defaultHidden: true, - }, - { - id: 'nav-logs-storage', - name: 'Storage Logs', - route: `/project/${ref}/logs/storage-logs`, - defaultHidden: true, - }, - { - id: 'nav-logs-realtime', - name: 'Realtime Logs', - route: `/project/${ref}/logs/realtime-logs`, - defaultHidden: true, - }, + ...(logsCollections + ? ([ + { + id: 'nav-logs-postgres', + name: 'Postgres Logs', + route: `/project/${ref}/logs/postgres-logs`, + defaultHidden: true, + }, + { + id: 'nav-logs-postgrest', + name: 'PostgREST Logs', + route: `/project/${ref}/logs/postgrest-logs`, + defaultHidden: true, + }, + { + id: 'nav-logs-pooler', + name: 'Pooler Logs', + route: `/project/${ref}/logs/pooler-logs`, + defaultHidden: true, + }, + { + id: 'nav-logs-auth', + name: 'Auth Logs', + route: `/project/${ref}/logs/auth-logs`, + defaultHidden: true, + }, + { + id: 'nav-logs-storage', + name: 'Storage Logs', + route: `/project/${ref}/logs/storage-logs`, + defaultHidden: true, + }, + { + id: 'nav-logs-realtime', + name: 'Realtime Logs', + route: `/project/${ref}/logs/realtime-logs`, + defaultHidden: true, + }, + ] as IRouteCommand[]) + : []), ], { ...options, deps: [ref] } ) diff --git a/apps/studio/components/layouts/ProjectLayout/BuildingState.tsx b/apps/studio/components/layouts/ProjectLayout/BuildingState.tsx index e52bae2142d..90f91b16710 100644 --- a/apps/studio/components/layouts/ProjectLayout/BuildingState.tsx +++ b/apps/studio/components/layouts/ProjectLayout/BuildingState.tsx @@ -5,7 +5,7 @@ import { useMemo } from 'react' import { useParams } from 'common' import ClientLibrary from 'components/interfaces/Home/ClientLibrary' -import ExampleProject from 'components/interfaces/Home/ExampleProject' +import { ExampleProject } from 'components/interfaces/Home/ExampleProject' import { CLIENT_LIBRARIES, EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants' import { DisplayApiSettings, DisplayConfigSettings } from 'components/ui/ProjectSettings' import { invalidateProjectDetailsQuery } from 'data/projects/project-detail-query' diff --git a/apps/studio/components/layouts/ProjectSettingsLayout/ProjectSettings.Commands.tsx b/apps/studio/components/layouts/ProjectSettingsLayout/ProjectSettings.Commands.tsx index 0a8b062f3f9..3710a00b12b 100644 --- a/apps/studio/components/layouts/ProjectSettingsLayout/ProjectSettings.Commands.tsx +++ b/apps/studio/components/layouts/ProjectSettingsLayout/ProjectSettings.Commands.tsx @@ -1,12 +1,21 @@ import { useParams } from 'common' import { COMMAND_MENU_SECTIONS } from 'components/interfaces/App/CommandMenu/CommandMenu.utils' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { CommandOptions } from 'ui-patterns/CommandMenu' import { useRegisterCommands } from 'ui-patterns/CommandMenu' +import { IRouteCommand } from 'ui-patterns/CommandMenu/internal/types' export function useProjectSettingsGotoCommands(options?: CommandOptions) { let { ref } = useParams() ref ||= '_' + const { projectSettingsLogDrains, projectSettingsCustomDomains, authenticationSignInProviders } = + useIsFeatureEnabled([ + 'project_settings:log_drains', + 'project_settings:custom_domains', + 'authentication:sign_in_providers', + ]) + useRegisterCommands( COMMAND_MENU_SECTIONS.NAVIGATE, [ @@ -25,7 +34,9 @@ export function useProjectSettingsGotoCommands(options?: CommandOptions) { { id: 'nav-project-settings-auth', name: 'Auth Settings', - route: `/project/${ref}/auth/providers`, + route: authenticationSignInProviders + ? `/project/${ref}/auth/providers` + : `/project/${ref}/auth/policies`, defaultHidden: true, }, { @@ -40,12 +51,16 @@ export function useProjectSettingsGotoCommands(options?: CommandOptions) { route: `/project/${ref}/storage/settings`, defaultHidden: true, }, - { - id: 'nav-project-settings-custom-domains', - name: 'Custom Domains', - route: `/project/${ref}/settings/general#custom-domains`, - defaultHidden: true, - }, + ...(projectSettingsCustomDomains + ? [ + { + id: 'nav-project-settings-custom-domains', + name: 'Custom Domains', + route: `/project/${ref}/settings/general#custom-domains`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), { id: 'nav-project-settings-restart-project', name: 'Restart project', @@ -106,12 +121,16 @@ export function useProjectSettingsGotoCommands(options?: CommandOptions) { route: `/project/${ref}/database/settings#banned-ips`, defaultHidden: true, }, - { - id: 'nav-project-settings-log-drains', - name: 'Log drains', - route: `/project/${ref}/settings/log-drains`, - defaultHidden: true, - }, + ...(projectSettingsLogDrains + ? [ + { + id: 'nav-project-settings-log-drains', + name: 'Log drains', + route: `/project/${ref}/settings/log-drains`, + defaultHidden: true, + } as IRouteCommand, + ] + : []), ], { ...options, deps: [ref] } ) diff --git a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.tsx b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.tsx index ceb8c60d8c9..b4a6b32e9fb 100644 --- a/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.tsx +++ b/apps/studio/components/layouts/ProjectSettingsLayout/SettingsMenu.utils.tsx @@ -128,7 +128,7 @@ export const generateSettingsMenu = ( { name: 'Authentication', key: 'auth', - url: `/project/${ref}/auth/policies`, + url: `/project/${ref}/auth/providers`, items: [], rightIcon: , }, diff --git a/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx b/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx index 22317d162f4..e1bc9f7b152 100644 --- a/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx +++ b/apps/studio/components/layouts/ReportsLayout/Reports.Commands.tsx @@ -1,5 +1,6 @@ import { useParams } from 'common' import { COMMAND_MENU_SECTIONS } from 'components/interfaces/App/CommandMenu/CommandMenu.utils' +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import type { CommandOptions } from 'ui-patterns/CommandMenu' import { useRegisterCommands } from 'ui-patterns/CommandMenu' @@ -7,40 +8,44 @@ export function useReportsGotoCommands(options?: CommandOptions) { let { ref } = useParams() ref ||= '_' + const { reportsAll } = useIsFeatureEnabled(['reports:all']) + useRegisterCommands( COMMAND_MENU_SECTIONS.NAVIGATE, - [ - { - id: 'nav-reports', - name: 'Reports', - route: `/project/${ref}/reports`, - defaultHidden: true, - }, - { - id: 'nav-reports-api', - name: 'API Reports', - route: `/project/${ref}/reports/api-overview`, - defaultHidden: true, - }, - { - id: 'nav-reports-storage', - name: 'Storage Reports', - route: `/project/${ref}/reports/storage`, - defaultHidden: true, - }, - { - id: 'nav-reports-database', - name: 'Database Reports', - route: `/project/${ref}/reports/database`, - defaultHidden: true, - }, - { - id: 'nav-reports-query-performance', - name: 'Query Performance Reports', - route: `/project/${ref}/reports/query-performance`, - defaultHidden: true, - }, - ], + reportsAll + ? [ + { + id: 'nav-reports', + name: 'Reports', + route: `/project/${ref}/reports`, + defaultHidden: true, + }, + { + id: 'nav-reports-api', + name: 'API Reports', + route: `/project/${ref}/reports/api-overview`, + defaultHidden: true, + }, + { + id: 'nav-reports-storage', + name: 'Storage Reports', + route: `/project/${ref}/reports/storage`, + defaultHidden: true, + }, + { + id: 'nav-reports-database', + name: 'Database Reports', + route: `/project/${ref}/reports/database`, + defaultHidden: true, + }, + { + id: 'nav-reports-query-performance', + name: 'Query Performance Reports', + route: `/project/${ref}/reports/query-performance`, + defaultHidden: true, + }, + ] + : [], { ...options, deps: [ref] } ) } 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 ( -
+
+ + { + 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 } } diff --git a/apps/studio/components/ui/GridFooter.tsx b/apps/studio/components/ui/GridFooter.tsx index 962422a6253..4e297d9b4c6 100644 --- a/apps/studio/components/ui/GridFooter.tsx +++ b/apps/studio/components/ui/GridFooter.tsx @@ -4,6 +4,7 @@ import { cn } from 'ui' export const GridFooter = ({ children, className }: PropsWithChildren<{ className?: string }>) => { return (
{ const { ref: projectRef } = useParams() - const { projectSettingsShowDisableLegacyApiKeys } = useIsFeatureEnabled([ - 'project_settings:show_disable_legacy_api_keys', - ]) const { data: settings, @@ -74,151 +69,145 @@ export const DisplayApiSettings = ({ }, [lastUsedLogData, apiKeys]) return ( - <> - -
Project API Keys
-

- 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. -
-

-
- ) - } - > - {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/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 = [ 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/hooks/custom-content/CustomContent.types.ts b/apps/studio/hooks/custom-content/CustomContent.types.ts new file mode 100644 index 00000000000..66816dffa96 --- /dev/null +++ b/apps/studio/hooks/custom-content/CustomContent.types.ts @@ -0,0 +1,33 @@ +import { ConnectionType } from 'components/interfaces/Connect/Connect.constants' + +export type CustomContentTypes = { + organizationLegalDocuments: { + id: string + name: string + description: string + action: { text: string; url: string } + }[] + + projectHomepageExampleProjects: { + title: string + description: string + iconUrl: string + url: string + }[] + + 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 new file mode 100644 index 00000000000..1378a1822a9 --- /dev/null +++ b/apps/studio/hooks/custom-content/custom-content.json @@ -0,0 +1,11 @@ +{ + "$schema": "./custom-content.schema.json", + + "organization:legal_documents": null, + + "project_homepage:example_projects": 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 new file mode 100644 index 00000000000..7992201514d --- /dev/null +++ b/apps/studio/hooks/custom-content/custom-content.sample.json @@ -0,0 +1,76 @@ +{ + "$schema": "./custom-content.schema.json", + + "organization:legal_documents": [ + { + "id": "doc1", + "name": "Document 1", + "description": "This is a description of Document 1", + "action": { + "text": "Download document", + "url": "https://supabase.com" + } + }, + { + "id": "doc2", + "name": "Document 2", + "description": "This is a description of Document 2", + "action": { + "text": "Download document", + "url": "https://supabase.com" + } + } + ], + + "project_homepage:example_projects": [ + { + "title": "Framework 1", + "description": "This is a description of Framework 1", + "iconUrl": "https://supabase.com/dashboard/img/supabase-logo.svg", + "url": "https://supabase.com" + }, + { + "title": "Framework 2", + "description": "This is a description of Framework 2", + "iconUrl": "https://supabase.com/dashboard/img/supabase-logo.svg", + "url": "https://supabase.com" + } + ], + + "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 new file mode 100644 index 00000000000..6d7020a3447 --- /dev/null +++ b/apps/studio/hooks/custom-content/custom-content.schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "$schema": { + "type": "string" + }, + + "organization:legal_documents": { + "type": ["array", "null"], + "description": "Renders a provided set of documents under the organization legal documents page", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "name": { "type": "string" }, + "description": { "type": "string" }, + "action": { + "type": "object", + "properties": { + "text": { "type": "string" }, + "url": { "type": "string" } + } + } + } + } + }, + + "project_homepage:example_projects": { + "type": ["array", "null"], + "description": "Renders a provided set of example projects under the project's home page", + "items": { + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "iconUrl": { "type": "string" }, + "url": { "type": "string" } + } + } + }, + + "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", + "connect:frameworks" + ], + "additionalProperties": false +} diff --git a/apps/studio/hooks/custom-content/useCustomContent.test.ts b/apps/studio/hooks/custom-content/useCustomContent.test.ts new file mode 100644 index 00000000000..e6ca936f758 --- /dev/null +++ b/apps/studio/hooks/custom-content/useCustomContent.test.ts @@ -0,0 +1,36 @@ +import { renderHook, cleanup } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +beforeEach(() => { + vi.clearAllMocks() + vi.resetModules() + cleanup() +}) + +describe('useCustomContent', () => { + it('should return null if content is not found in the custom-content.json file', async () => { + vi.doMock('./custom-content.json', () => ({ + default: { + 'organization:legal_documents': null, + }, + })) + + const { useCustomContent } = await import('./useCustomContent') + const { result } = renderHook(() => useCustomContent(['organization:legal_documents'])) + expect(result.current.organizationLegalDocuments).toEqual(null) + }) + + it('should return the content for the key passed in if it exists in the custom-content.json file', async () => { + vi.doMock('./custom-content.json', () => ({ + default: { + 'organization:legal_documents': { + someValue: 'foo', + }, + }, + })) + + const { useCustomContent } = await import('./useCustomContent') + const { result } = renderHook(() => useCustomContent(['organization:legal_documents'])) + expect(result.current.organizationLegalDocuments).toEqual({ someValue: 'foo' }) + }) +}) diff --git a/apps/studio/hooks/custom-content/useCustomContent.ts b/apps/studio/hooks/custom-content/useCustomContent.ts new file mode 100644 index 00000000000..0d1b2342642 --- /dev/null +++ b/apps/studio/hooks/custom-content/useCustomContent.ts @@ -0,0 +1,44 @@ +import customContentRaw from './custom-content.json' +import { CustomContentTypes } from './CustomContent.types' + +// [Joshen] See if we can de-dupe any of the logic here with enabled-features +// For now just getting something working going first +// Also not sure if CustomContentTypes is the right way to go here with trying to dynamically type + +const customContentStaticObj = customContentRaw as Omit +type CustomContent = keyof typeof customContentStaticObj + +type SnakeToCamelCase = S extends `${infer First}_${infer Rest}` + ? `${First}${SnakeToCamelCase>}` + : S + +type CustomContentToCamelCase = S extends `${infer P}:${infer R}` + ? `${SnakeToCamelCase

}${Capitalize>}` + : SnakeToCamelCase + +function contentToCamelCase(feature: CustomContent) { + return feature + .replace(/:/g, '_') + .split('_') + .map((word, index) => (index === 0 ? word : word[0].toUpperCase() + word.slice(1))) + .join('') as CustomContentToCamelCase +} + +const useCustomContent = ( + contents: T +): { + [key in CustomContentToCamelCase]: + | (typeof customContentStaticObj)[CustomContent] + | CustomContentTypes[CustomContentToCamelCase] +} => { + // [Joshen] Running into some TS errors without the `as` here - must be overlooking something super simple + return Object.fromEntries( + contents.map((content) => [contentToCamelCase(content), customContentStaticObj[content]]) + ) as { + [key in CustomContentToCamelCase]: + | (typeof customContentStaticObj)[CustomContent] + | CustomContentTypes[CustomContentToCamelCase] + } +} + +export { useCustomContent } diff --git a/apps/studio/instrumentation-client.ts b/apps/studio/instrumentation-client.ts index 06eca90031d..e1543a0e150 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+$/, @@ -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/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/studio/pages/project/[ref]/index.tsx b/apps/studio/pages/project/[ref]/index.tsx index f075fd2b228..567ecaa1800 100644 --- a/apps/studio/pages/project/[ref]/index.tsx +++ b/apps/studio/pages/project/[ref]/index.tsx @@ -3,8 +3,9 @@ import Link from 'next/link' import { useEffect, useMemo, useRef } from 'react' import { useParams } from 'common' -import { ClientLibrary, ExampleProject } from 'components/interfaces/Home' +import { ClientLibrary } from 'components/interfaces/Home' import { AdvisorWidget } from 'components/interfaces/Home/AdvisorWidget' +import { ExampleProject } from 'components/interfaces/Home/ExampleProject' import { CLIENT_LIBRARIES, EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants' import { NewProjectPanel } from 'components/interfaces/Home/NewProjectPanel/NewProjectPanel' import { ProjectUsageSection } from 'components/interfaces/Home/ProjectUsageSection' @@ -19,6 +20,7 @@ import { useBranchesQuery } from 'data/branches/branches-query' import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query' import { useReadReplicasQuery } from 'data/read-replicas/replicas-query' import { useTablesQuery } from 'data/tables/tables-query' +import { useCustomContent } from 'hooks/custom-content/useCustomContent' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { @@ -50,6 +52,8 @@ const Home: NextPageWithLayout = () => { const snap = useAppStateSnapshot() const { ref, enableBranching } = useParams() + const { projectHomepageExampleProjects } = useCustomContent(['project_homepage:example_projects']) + const { projectHomepageShowAllClientLibraries: showAllClientLibraries, projectHomepageShowInstanceSize: showInstanceSize, @@ -243,36 +247,46 @@ const Home: NextPageWithLayout = () => {

{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) => ( + + ))} +
+
+
+
+ )}
)} 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([]) 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/apps/studio/pages/reset-password.tsx b/apps/studio/pages/reset-password.tsx index 67585813e65..d4aec3b39b9 100644 --- a/apps/studio/pages/reset-password.tsx +++ b/apps/studio/pages/reset-password.tsx @@ -1,5 +1,3 @@ -import Link from 'next/link' - import ResetPasswordForm from 'components/interfaces/SignIn/ResetPasswordForm' import ForgotPasswordLayout from 'components/layouts/SignInLayout/ForgotPasswordLayout' import { withAuth } from 'hooks/misc/withAuth' @@ -7,11 +5,9 @@ import type { NextPageWithLayout } from 'types' const ResetPasswordPage: NextPageWithLayout = () => { return ( - <> -
- -
- +
+ +
) } diff --git a/apps/studio/pages/sign-in-fly-tos.tsx b/apps/studio/pages/sign-in-fly-tos.tsx index 4a6fad54c3a..4ee0d38cc80 100644 --- a/apps/studio/pages/sign-in-fly-tos.tsx +++ b/apps/studio/pages/sign-in-fly-tos.tsx @@ -7,7 +7,7 @@ import { useEffect, useState } from 'react' import { useIsLoggedIn } from 'common' import { useOrganizationByFlyOrgIdMutation } from 'data/organizations/organization-by-fly-organization-id-mutation' import { useProjectByFlyExtensionIdMutation } from 'data/projects/project-by-fly-extension-id-mutation' -import { API_URL } from 'lib/constants' +import { API_URL, BASE_PATH } from 'lib/constants' import { Button } from 'ui' const SignInFlyTos = () => { @@ -78,8 +78,8 @@ const SignInFlyTos = () => { { + return +} + +SignInPartnerPage.getLayout = (page) => ( + // [Joshen] Just using this layout for the styling + div:first-child]:absolute [&>div:first-child]:!px-0', + '[&>div:last-child]:flex-grow [&>div:last-child>main]:!px-0' + )} + > + {page} + +) + +export default SignInPartnerPage 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 ( <> -