From b5bfc96afe16426cb9d22918edb8d5e9120709d9 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton <70828596+Isaiah-Hamilton@users.noreply.github.com> Date: Mon, 26 Jun 2023 21:14:07 -0400 Subject: [PATCH 01/12] update supasquad link --- apps/www/pages/supasquad.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/www/pages/supasquad.mdx b/apps/www/pages/supasquad.mdx index 04eee13ef51..21cfa6f902c 100644 --- a/apps/www/pages/supasquad.mdx +++ b/apps/www/pages/supasquad.mdx @@ -37,7 +37,7 @@ Spread the word on social channels and help to answer Supabase-related questions ### Builder -Build Supabase examples, blog about them, and add them to the [SupaSquad GitHub Org](https://github.com/supasquad). +Build Supabase examples, a blog, and documentation and contribute them to the [main Supabae repo](https://github.com/supabase/supabase). ### Author @@ -59,7 +59,6 @@ Help us maintain the community guidelines in our GitHub and Community-led commun - Free credits that you can use for Squad efforts. - Direct access to members of the Supabase team for questions, suggestions, etc. - Help shape the future of the program. -- Invited to the [SupaSquad GitHub Org](https://github.com/supasquad) to collaborate with the other squad members. - Exclusive Supabase Team swag drops are usually exclusively reserved for the Supabase core team. ## How to join From 79505a5efd9b947adfde1de627c47799aac93e52 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Tue, 27 Jun 2023 01:38:46 -0700 Subject: [PATCH 02/12] chore: update cli reference doc --- spec/cli_v1_commands.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/cli_v1_commands.yaml b/spec/cli_v1_commands.yaml index 42cfd39fcbf..e9954490c40 100644 --- a/spec/cli_v1_commands.yaml +++ b/spec/cli_v1_commands.yaml @@ -1,7 +1,7 @@ clispec: '001' info: id: cli - version: 1.74.0 + version: 1.74.2 title: Supabase CLI language: sh source: https://github.com/supabase/cli From 87b2c3518f757c3b665c54bfae3e1d845dd162a4 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Tue, 27 Jun 2023 19:21:11 -0400 Subject: [PATCH 03/12] Update apps/www/pages/supasquad.mdx Co-authored-by: Greg Richardson --- apps/www/pages/supasquad.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/pages/supasquad.mdx b/apps/www/pages/supasquad.mdx index 21cfa6f902c..17fa4496aef 100644 --- a/apps/www/pages/supasquad.mdx +++ b/apps/www/pages/supasquad.mdx @@ -37,7 +37,7 @@ Spread the word on social channels and help to answer Supabase-related questions ### Builder -Build Supabase examples, a blog, and documentation and contribute them to the [main Supabae repo](https://github.com/supabase/supabase). +Build Supabase examples, blog about them, and add them to the [Supabase repo](https://github.com/supabase/supabase/tree/master/examples). ### Author From e4a5f635cf170fb13644931a3e32fdc4dc9c58f1 Mon Sep 17 00:00:00 2001 From: dragarcia Date: Wed, 28 Jun 2023 08:51:12 +0800 Subject: [PATCH 04/12] chore: temporarily disable us east --- studio/lib/constants/infrastructure.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/lib/constants/infrastructure.ts b/studio/lib/constants/infrastructure.ts index eed060ab177..13a8d9ca998 100644 --- a/studio/lib/constants/infrastructure.ts +++ b/studio/lib/constants/infrastructure.ts @@ -7,7 +7,7 @@ export type Region = typeof AWS_REGIONS | typeof FLY_REGIONS export const AWS_REGIONS = { WEST_US: 'West US (North California)', - EAST_US: 'East US (North Virginia)', + // EAST_US: 'East US (North Virginia)', CENTRAL_CANADA: 'Canada (Central)', WEST_EU: 'West EU (Ireland)', WEST_EU_2: 'West EU (London)', @@ -27,7 +27,7 @@ export const FLY_REGIONS = { } as const export const AWS_REGIONS_DEFAULT = - process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' ? AWS_REGIONS.SOUTHEAST_ASIA : AWS_REGIONS.EAST_US + process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' ? AWS_REGIONS.SOUTHEAST_ASIA : AWS_REGIONS.WEST_US // TO DO, change default to US region for prod export const FLY_REGIONS_DEFAULT = FLY_REGIONS.SOUTHEAST_ASIA From 0616d5280a7677d7521e84d7eba02b707c46b7e2 Mon Sep 17 00:00:00 2001 From: angelico Date: Wed, 28 Jun 2023 11:36:51 +0800 Subject: [PATCH 05/12] Revert "hotfix: temporarily disable us east" --- studio/lib/constants/infrastructure.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/lib/constants/infrastructure.ts b/studio/lib/constants/infrastructure.ts index 13a8d9ca998..eed060ab177 100644 --- a/studio/lib/constants/infrastructure.ts +++ b/studio/lib/constants/infrastructure.ts @@ -7,7 +7,7 @@ export type Region = typeof AWS_REGIONS | typeof FLY_REGIONS export const AWS_REGIONS = { WEST_US: 'West US (North California)', - // EAST_US: 'East US (North Virginia)', + EAST_US: 'East US (North Virginia)', CENTRAL_CANADA: 'Canada (Central)', WEST_EU: 'West EU (Ireland)', WEST_EU_2: 'West EU (London)', @@ -27,7 +27,7 @@ export const FLY_REGIONS = { } as const export const AWS_REGIONS_DEFAULT = - process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' ? AWS_REGIONS.SOUTHEAST_ASIA : AWS_REGIONS.WEST_US + process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' ? AWS_REGIONS.SOUTHEAST_ASIA : AWS_REGIONS.EAST_US // TO DO, change default to US region for prod export const FLY_REGIONS_DEFAULT = FLY_REGIONS.SOUTHEAST_ASIA From 7c9948c172cc2b6a9d20ddc206c9d719c185c494 Mon Sep 17 00:00:00 2001 From: Div Arora Date: Wed, 28 Jun 2023 11:40:16 +0800 Subject: [PATCH 06/12] Retaining usw1 as the default region for now --- studio/lib/constants/infrastructure.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/lib/constants/infrastructure.ts b/studio/lib/constants/infrastructure.ts index eed060ab177..a4f508806c2 100644 --- a/studio/lib/constants/infrastructure.ts +++ b/studio/lib/constants/infrastructure.ts @@ -27,7 +27,7 @@ export const FLY_REGIONS = { } as const export const AWS_REGIONS_DEFAULT = - process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' ? AWS_REGIONS.SOUTHEAST_ASIA : AWS_REGIONS.EAST_US + process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' ? AWS_REGIONS.SOUTHEAST_ASIA : AWS_REGIONS.WEST_US // TO DO, change default to US region for prod export const FLY_REGIONS_DEFAULT = FLY_REGIONS.SOUTHEAST_ASIA From b1c23e41f0854a920d165bca726a58b7521b8ba8 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Wed, 28 Jun 2023 04:43:08 -0700 Subject: [PATCH 07/12] chore: update cli reference doc --- spec/cli_v1_commands.yaml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spec/cli_v1_commands.yaml b/spec/cli_v1_commands.yaml index e9954490c40..91f0d6d6b3f 100644 --- a/spec/cli_v1_commands.yaml +++ b/spec/cli_v1_commands.yaml @@ -1,7 +1,7 @@ clispec: '001' info: id: cli - version: 1.74.2 + version: 1.75.1 title: Supabase CLI language: sh source: https://github.com/supabase/cli @@ -994,6 +994,7 @@ commands: - management-api links: [] subcommands: + - supabase-orgs-create - supabase-orgs-list flags: [] - id: supabase-orgs-list @@ -1005,6 +1006,15 @@ commands: usage: supabase orgs list subcommands: [] flags: [] + - id: supabase-orgs-create + title: supabase orgs create + summary: Create an organization + description: Create an organization for the logged-in user. + tags: [] + links: [] + usage: supabase orgs create + subcommands: [] + flags: [] - id: supabase-network-restrictions title: supabase network-restrictions summary: Manage network restrictions From e0a6dbab767547e1d6db4d2390604f774d38f5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Mon, 26 Jun 2023 19:50:16 +0200 Subject: [PATCH 08/12] feat: transfer project to another org --- .../TransferProjectButton.tsx | 348 ++++++++++++++++++ .../TransferProjectPanel.tsx | 46 +++ .../interfaces/Settings/General/index.ts | 12 +- studio/components/ui/UpgradeToPro.tsx | 5 +- studio/data/projects/keys.ts | 6 + .../projects/project-transfer-mutation.ts | 60 +++ .../project-transfer-preview-query.ts | 123 +++++++ .../pages/project/[ref]/settings/general.tsx | 5 + 8 files changed, 601 insertions(+), 4 deletions(-) create mode 100644 studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx create mode 100644 studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectPanel.tsx create mode 100644 studio/data/projects/project-transfer-mutation.ts create mode 100644 studio/data/projects/project-transfer-preview-query.ts diff --git a/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx b/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx new file mode 100644 index 00000000000..3aee3566e35 --- /dev/null +++ b/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx @@ -0,0 +1,348 @@ +import { FC, useEffect, useMemo, useState } from 'react' +import { + Alert, + Button, + IconLoader, + IconShield, + IconTool, + IconUsers, + Listbox, + Loading, + Modal, +} from 'ui' +import * as Tooltip from '@radix-ui/react-tooltip' +import { PermissionAction } from '@supabase/shared-types/out/constants' + +import { useStore, useSelectedProject, useCheckPermissions } from 'hooks' +import { useProjectTransferPreviewQuery } from 'data/projects/project-transfer-preview-query' +import { useProjectTransferMutation } from 'data/projects/project-transfer-mutation' +import { useOrganizationsQuery } from 'data/organizations/organizations-query' +import Link from 'next/link' + +const TransferProjectButton: FC<{}> = () => { + const { ui } = useStore() + + const project = useSelectedProject() + const projectRef = project?.ref + const projectOrgId = project?.organization_id + const { data: allOrganizations } = useOrganizationsQuery() + + const organizations = useMemo( + () => + (allOrganizations || []) + .filter((it) => it.id !== projectOrgId) + // Only orgs with org-level subscription + .filter((it) => it.subscription_id), + [allOrganizations, projectOrgId] + ) + + const [isOpen, setIsOpen] = useState(false) + const [isLoading, setIsLoading] = useState(false) + + const [selectedOrg, setSelectedOrg] = useState() + + const { error: transferError, mutateAsync: transferProject } = useProjectTransferMutation() + + const { + data: transferPreviewData, + error: transferPreviewError, + isLoading: transferPreviewIsLoading, + remove, + } = useProjectTransferPreviewQuery( + { projectRef, targetOrganizationSlug: selectedOrg }, + { enabled: !isLoading && isOpen } + ) + + useEffect(() => { + if (isOpen) { + // reset state + setSelectedOrg(undefined) + setIsLoading(false) + } else { + // Invalidate cache + remove() + } + }, [isOpen]) + + const canTransferProject = useCheckPermissions(PermissionAction.UPDATE, 'projects') + + const toggle = () => { + setIsOpen(!isOpen) + } + + async function handleTransferProject() { + if (project === undefined) return + if (selectedOrg === undefined) return + + setIsLoading(true) + + try { + await transferProject({ projectRef, targetOrganizationSlug: selectedOrg }) + ui.setNotification({ + category: 'success', + + duration: 5000, + message: `Successfully transferred project ${project?.name}.`, + }) + setIsOpen(false) + } catch { + } finally { + setIsLoading(false) + } + } + + return ( + <> + + + + + {!canTransferProject && ( + + + +
+ + You need additional permissions to delete this project + +
+
+
+ )} +
+ + toggle()} + visible={isOpen} + loading={isLoading} + size={'xlarge'} + header={`Transfer project ${project?.name}`} + customFooter={ +
+ + +
+ } + > +
+ +

+ To transfer projects, the owner must be a member of both the source and target + organizations. +

+ +

Transferring considerations:

+ +
    +
  • + + + +
    +

    No downtime

    +

    There is no downtime or restrictions involved when transferring a project.

    +
    +
  • + +
  • + + + +
    +

    Permissions

    +

    + Depending on your role in the target organization, your level of permissions may + change after transfer. +

    +
    +
  • + +
  • + + + +
    +

    Features

    +

    + Moving your project to an organization with a smaller subscription plan may + result in the loss of certain features (i.e. image transformations). +

    +
    +
  • +
+
+ + {organizations.length > 0 && ( +
+ setSelectedOrg(slug)} + placeholder="Select Organization" + > + + Select Organization + + {organizations.map((x: any) => ( + } + > + {x.name} + + ))} + +

+ The target organization needs to use{' '} + + + organization-level-billing + + + . If you're looking to migrate an organization to the new billing, head to your{' '} + + + organizations settings + + + . +

+
+ )} +
+ + + +
+ {transferPreviewData && transferPreviewData.valid && ( +
+ {transferPreviewData.source_subscription_plan !== + transferPreviewData.target_subscription_plan ? ( +
+

+ Your project is currently on the{' '} + {transferPreviewData.source_subscription_plan} plan, whereas the target + organization uses the {transferPreviewData.target_subscription_plan} plan. +

+
+ ) : ( +
+ Your project and the target organization are both on the{' '} + {transferPreviewData.source_subscription_plan} subscription plan. +
+ )} + +
+ {transferPreviewData.credits_on_source_organization === 0 ? ( + + {' '} + Your current organization won't be granted any prorated credits. + + ) : ( + + {' '} + Your current organization will be granted{' '} + + ${transferPreviewData.credits_on_source_organization} + {' '} + in credits as proration. + + )} + {transferPreviewData.costs_on_target_organization === 0 ? ( + + {' '} + The target organization won't be charged any immediate upfront payment. + + ) : ( + + {' '} + The target organization will be billed{' '} + + ${transferPreviewData.costs_on_target_organization} + {' '} + immediately to prorate for the remainder of the billing period. + + )} +
+
+ )} + {transferPreviewData && transferPreviewData.warnings.length > 0 && ( + +
+ {transferPreviewData.warnings.map((warning) => ( +

{warning.message}

+ ))} +
+
+ )} + {transferPreviewData && transferPreviewData.errors.length > 0 && ( + +
+ {transferPreviewData.errors.map((error) => ( +

{error.message}

+ ))} +
+ {transferPreviewData.members_exceeding_free_project_limit.length > 0 && ( +
+

+ These members have reached their maximum limits for the number of active + Free plan projects within organizations where they are an administrator or + owner: +

+
    + {(transferPreviewData.members_exceeding_free_project_limit || []).map( + (member, idx: number) => ( +
  • + {member.name} (Limit: {member.limit} free projects) +
  • + ) + )} +
+

+ These members will need to either delete, pause, or upgrade one or more of + their projects before you can downgrade this project. +

+
+ )} +
+ )} + {transferPreviewError && !transferError && ( + +

{transferPreviewError.message}

+
+ )} + {transferError && ( + +

{transferError.message}

+
+ )} +
+
+
+
+
+ + ) +} + +export default TransferProjectButton diff --git a/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectPanel.tsx b/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectPanel.tsx new file mode 100644 index 00000000000..ff8ce40428d --- /dev/null +++ b/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectPanel.tsx @@ -0,0 +1,46 @@ +import { FC } from 'react' + +import { useSelectedProject } from 'hooks' +import Panel from 'components/ui/Panel' + +import TransferProjectButton from './TransferProjectButton' +import { FormHeader } from 'components/ui/Forms' +import { IconTruck } from 'ui' + +interface Props {} + +const TransferProjectPanel: FC = ({}) => { + const project = useSelectedProject() + + if (project === undefined) return <> + + return ( +
+ + + +
+
+ +
+

Transfer project to another organization

+

+ To transfer projects, the owner must be a member of both the source and target + organizations. +

+
+
+
+ +
+
+
+
+
+ ) +} + +export default TransferProjectPanel diff --git a/studio/components/interfaces/Settings/General/index.ts b/studio/components/interfaces/Settings/General/index.ts index 8ff5b36ea73..ecd3345f01c 100644 --- a/studio/components/interfaces/Settings/General/index.ts +++ b/studio/components/interfaces/Settings/General/index.ts @@ -3,5 +3,15 @@ import Infrastructure from './Infrastructure/Infrastructure' import CustomDomainConfig from './CustomDomainConfig/CustomDomainConfig' import DeleteProjectPanel from './DeleteProjectPanel/DeleteProjectPanel' import DeleteProjectButton from './DeleteProjectPanel/DeleteProjectButton' +import TransferProjectButton from './TransferProjectPanel/TransferProjectButton' +import TransferProjectPanel from './TransferProjectPanel/TransferProjectPanel' -export { General, Infrastructure, CustomDomainConfig, DeleteProjectPanel, DeleteProjectButton } +export { + General, + Infrastructure, + CustomDomainConfig, + DeleteProjectPanel, + DeleteProjectButton, + TransferProjectPanel, + TransferProjectButton, +} diff --git a/studio/components/ui/UpgradeToPro.tsx b/studio/components/ui/UpgradeToPro.tsx index e1771980505..fe0a055afa9 100644 --- a/studio/components/ui/UpgradeToPro.tsx +++ b/studio/components/ui/UpgradeToPro.tsx @@ -26,7 +26,6 @@ const UpgradeToPro: FC = ({ icon, primaryText, projectRef, secondaryText 'stripe.subscriptions' ) const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate') - const isEnterprise = tier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE return (
= ({ icon, primaryText, projectRef, secondaryText diff --git a/studio/data/projects/keys.ts b/studio/data/projects/keys.ts index 0726abeaf9e..8e3e5c10db0 100644 --- a/studio/data/projects/keys.ts +++ b/studio/data/projects/keys.ts @@ -1,4 +1,10 @@ export const projectKeys = { list: () => ['all-projects'] as const, detail: (projectRef: string | undefined) => ['project', projectRef, 'detail'] as const, + projectTransfer: (projectRef: string | undefined, targetOrganizationSlug: string | undefined) => + ['projects', 'transfer', projectRef, targetOrganizationSlug] as const, + projectTransferPreview: ( + projectRef: string | undefined, + targetOrganizationSlug: string | undefined + ) => ['projects', 'transfer', projectRef, targetOrganizationSlug, 'preview'] as const, } diff --git a/studio/data/projects/project-transfer-mutation.ts b/studio/data/projects/project-transfer-mutation.ts new file mode 100644 index 00000000000..45cfe5abdcc --- /dev/null +++ b/studio/data/projects/project-transfer-mutation.ts @@ -0,0 +1,60 @@ +import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query' +import { post } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { projectKeys } from './keys' + +export type ProjectTransferVariables = { + projectRef?: string + targetOrganizationSlug?: string +} + +type ProjectTransferError = { + message: string +} + +export async function transferProject({ + projectRef, + targetOrganizationSlug, +}: ProjectTransferVariables) { + if (!projectRef) throw new Error('projectRef is required') + if (!targetOrganizationSlug) throw new Error('targetOrganizationSlug is required') + + const payload: { target_organization_slug: string } = { + target_organization_slug: targetOrganizationSlug, + } + + const response = await post(`${API_URL}/projects/${projectRef}/transfer`, payload) + if (response.error) throw response.error + + return response +} + +type ProjectTransferData = Awaited> + +export const useProjectTransferMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions, + 'mutationFn' +> = {}) => { + const queryClient = useQueryClient() + + return useMutation( + (vars) => transferProject(vars), + { + async onSuccess(data, variables, context) { + const { projectRef, targetOrganizationSlug } = variables + await Promise.all([ + queryClient.invalidateQueries( + projectKeys.projectTransferPreview(projectRef, targetOrganizationSlug) + ), + queryClient.invalidateQueries(projectKeys.detail(projectRef)), + queryClient.invalidateQueries(projectKeys.list()), + ]) + await onSuccess?.(data, variables, context) + }, + ...options, + } + ) +} diff --git a/studio/data/projects/project-transfer-preview-query.ts b/studio/data/projects/project-transfer-preview-query.ts new file mode 100644 index 00000000000..a5edae66caf --- /dev/null +++ b/studio/data/projects/project-transfer-preview-query.ts @@ -0,0 +1,123 @@ +import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query' +import { post } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' +import { useCallback } from 'react' +import { projectKeys } from './keys' + +export type ProjectTransferPreviewVariables = { + projectRef?: string + targetOrganizationSlug?: string +} + +export type PlanId = 'free' | 'pro' | 'team' | 'enterprise' + +type MemberExceedingFreeProjectLimit = { + name: string + limit: number +} + +type PreviewTransferInfo = { + key: string + message: string +} + +export type PreviewProjectTransferResponse = { + valid: boolean + + warnings: PreviewTransferInfo[] + errors: PreviewTransferInfo[] + + members_exceeding_free_project_limit: MemberExceedingFreeProjectLimit[] + + has_permissions_on_source_organization: boolean + has_access_to_target_organization: boolean + + source_project_eligible: boolean + + target_organization_eligible: boolean | null + target_organization_has_free_project_slots: boolean | null + + credits_on_source_organization: number + costs_on_target_organization: number + charge_on_target_organization: number + + source_subscription_plan: PlanId + target_subscription_plan: PlanId | null +} + +export async function previewProjectTransfer( + { projectRef, targetOrganizationSlug }: ProjectTransferPreviewVariables, + signal?: AbortSignal +) { + if (!projectRef) throw new Error('projectRef is required') + if (!targetOrganizationSlug) throw new Error('targetOrganizationSlug is required') + + const response = await post( + `${API_URL}/projects/${projectRef}/transfer/preview`, + { + target_organization_slug: targetOrganizationSlug, + }, + { signal } + ) + if (response.error) throw response.error + + return response as PreviewProjectTransferResponse +} + +export type ProjectTransferPreviewData = Awaited> +export type ProjectTransferPreviewError = { + message: string +} + +export const useProjectTransferPreviewQuery = ( + { projectRef, targetOrganizationSlug }: ProjectTransferPreviewVariables, + { + enabled = true, + ...options + }: UseQueryOptions = {} +) => + useQuery( + projectKeys.projectTransferPreview(projectRef, targetOrganizationSlug), + ({ signal }) => previewProjectTransfer({ projectRef, targetOrganizationSlug }, signal), + { + enabled: + enabled && + typeof projectRef !== 'undefined' && + typeof targetOrganizationSlug !== 'undefined', + ...options, + + retry: (failureCount, error) => { + // Don't retry on 400s + if ( + typeof error === 'object' && + error !== null && + 'code' in error && + (error as any).code === 400 + ) { + return false + } + + if (failureCount < 3) { + return true + } + + return false + }, + } + ) + +export const useProjectTransferPreviewPrefetch = ({ + projectRef, + targetOrganizationSlug, +}: ProjectTransferPreviewVariables) => { + const client = useQueryClient() + + return useCallback(() => { + if (projectRef && targetOrganizationSlug) { + client.prefetchQuery( + projectKeys.projectTransferPreview(projectRef, targetOrganizationSlug), + ({ signal }) => previewProjectTransfer({ projectRef, targetOrganizationSlug }, signal) + ) + } + }, [projectRef, targetOrganizationSlug]) +} diff --git a/studio/pages/project/[ref]/settings/general.tsx b/studio/pages/project/[ref]/settings/general.tsx index a1981c1e607..9049ec621ac 100644 --- a/studio/pages/project/[ref]/settings/general.tsx +++ b/studio/pages/project/[ref]/settings/general.tsx @@ -7,15 +7,20 @@ import { Infrastructure, CustomDomainConfig, DeleteProjectPanel, + TransferProjectPanel, } from 'components/interfaces/Settings/General' +import { useFlag } from 'hooks' const ProjectSettings: NextPageWithLayout = () => { + const transferProjectEnabled = useFlag('transferProject') + // [Joshen] Opting for larger gap instead of gap-8 as compared to other pages for better grouping of content return (
+ {transferProjectEnabled && }
) From 3e22129fbe7652c096c9405b89cc5058c3686894 Mon Sep 17 00:00:00 2001 From: Kang Ming Date: Thu, 29 Jun 2023 01:49:23 +0800 Subject: [PATCH 09/12] feat: add kakao provider to dashboard --- studio/pages/api/auth/[ref]/config.ts | 3 ++ studio/public/img/icons/kakao-icon.svg | 1 + .../AuthProvidersFormValidation.tsx | 41 +++++++++++++++++++ .../stores/jsonSchema/auth_gotrue_config.json | 13 ++++++ 4 files changed, 58 insertions(+) create mode 100644 studio/public/img/icons/kakao-icon.svg diff --git a/studio/pages/api/auth/[ref]/config.ts b/studio/pages/api/auth/[ref]/config.ts index 66545dba0ae..80f655a588d 100644 --- a/studio/pages/api/auth/[ref]/config.ts +++ b/studio/pages/api/auth/[ref]/config.ts @@ -74,6 +74,9 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { EXTERNAL_GOOGLE_ENABLED: false, EXTERNAL_GOOGLE_CLIENT_ID: null, EXTERNAL_GOOGLE_SECRET: null, + EXTERNAL_KAKAO_ENABLED: false, + EXTERNAL_KAKAO_CLIENT_ID: null, + EXTERNAL_KAKAO_SECRET: null, EXTERNAL_KEYCLOAK_ENABLED: false, EXTERNAL_KEYCLOAK_CLIENT_ID: null, EXTERNAL_KEYCLOAK_SECRET: null, diff --git a/studio/public/img/icons/kakao-icon.svg b/studio/public/img/icons/kakao-icon.svg new file mode 100644 index 00000000000..e721ec2b432 --- /dev/null +++ b/studio/public/img/icons/kakao-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx b/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx index 3020af6abec..48eefc8e24e 100644 --- a/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx +++ b/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx @@ -784,6 +784,46 @@ const EXTERNAL_PROVIDER_GOOGLE = { }, } +const EXTERNAL_PROVIDER_KAKAO = { + $schema: JSON_SCHEMA_VERSION, + type: 'object', + title: 'Kakao', + properties: { + EXTERNAL_KAKAO_ENABLED: { + title: 'Kakao enabled', + type: 'boolean', + }, + // [TODO] Update docs + EXTERNAL_KAKAO_CLIENT_ID: { + title: 'REST API Key', + type: 'string', + }, + // [TODO] Update docs + EXTERNAL_KAKAO_SECRET: { + title: 'Client Secret Code', + type: 'string', + isSecret: true, + }, + }, + validationSchema: object().shape({ + EXTERNAL_KAKAO_ENABLED: boolean().required(), + EXTERNAL_KAKAO_CLIENT_ID: string().when('EXTERNAL_KAKAO_ENABLED', { + is: true, + then: (schema) => schema.required('REST API Key is required'), + otherwise: (schema) => schema, + }), + EXTERNAL_KAKAO_SECRET: string().when('EXTERNAL_KAKAO_ENABLED', { + is: true, + then: (schema) => schema.required('Client Secret Code is required'), + otherwise: (schema) => schema, + }), + }), + misc: { + iconKey: 'kakao-icon', + requiresRedirect: true, + }, +} + // [TODO]: clarify the EXTERNAL_KEYCLOAK_URL property const EXTERNAL_PROVIDER_KEYCLOAK = { $schema: JSON_SCHEMA_VERSION, @@ -1183,6 +1223,7 @@ export const PROVIDERS_SCHEMAS = [ EXTERNAL_PROVIDER_GITHUB, EXTERNAL_PROVIDER_GITLAB, EXTERNAL_PROVIDER_GOOGLE, + EXTERNAL_PROVIDER_KAKAO, EXTERNAL_PROVIDER_KEYCLOAK, EXTERNAL_PROVIDER_LINKEDIN, EXTERNAL_PROVIDER_NOTION, diff --git a/studio/stores/jsonSchema/auth_gotrue_config.json b/studio/stores/jsonSchema/auth_gotrue_config.json index 55496508ce3..e190bc15bac 100644 --- a/studio/stores/jsonSchema/auth_gotrue_config.json +++ b/studio/stores/jsonSchema/auth_gotrue_config.json @@ -17,6 +17,7 @@ "EXTERNAL_GITHUB_ENABLED", "EXTERNAL_GITLAB_ENABLED", "EXTERNAL_GOOGLE_ENABLED", + "EXTERNAL_KAKAO_ENABLED", "EXTERNAL_KEYCLOAK_ENABLED", "EXTERNAL_LINKEDIN_ENABLED", "EXTERNAL_NOTION_ENABLED", @@ -196,6 +197,18 @@ "title": "Google secret", "type": "string" }, + "EXTERNAL_KAKAO_ENABLED": { + "title": "Kakao enabled", + "type": "boolean" + }, + "EXTERNAL_KAKAO_CLIENT_ID": { + "title": "Kakao client ID", + "type": "string" + }, + "EXTERNAL_KAKAO_SECRET": { + "title": "Kakao secret", + "type": "string" + }, "EXTERNAL_KEYCLOAK_ENABLED": { "title": "Keycloak enabled", "type": "boolean" From bfc664f3d3434ddbd1ac77817b6edf07f1bade65 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 28 Jun 2023 16:10:39 -0230 Subject: [PATCH 10/12] Conditionally show project dropdown --- .../TransferProjectButton.tsx | 74 +++++++++++-------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx b/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx index 3aee3566e35..d98c2229d8b 100644 --- a/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx +++ b/studio/components/interfaces/Settings/General/TransferProjectPanel/TransferProjectButton.tsx @@ -2,6 +2,7 @@ import { FC, useEffect, useMemo, useState } from 'react' import { Alert, Button, + IconAlertCircle, IconLoader, IconShield, IconTool, @@ -27,14 +28,10 @@ const TransferProjectButton: FC<{}> = () => { const projectOrgId = project?.organization_id const { data: allOrganizations } = useOrganizationsQuery() - const organizations = useMemo( - () => - (allOrganizations || []) - .filter((it) => it.id !== projectOrgId) - // Only orgs with org-level subscription - .filter((it) => it.subscription_id), - [allOrganizations, projectOrgId] - ) + const organizations = (allOrganizations || []) + .filter((it) => it.id !== projectOrgId) + // Only orgs with org-level subscription + .filter((it) => it.subscription_id) const [isOpen, setIsOpen] = useState(false) const [isLoading, setIsLoading] = useState(false) @@ -143,7 +140,7 @@ const TransferProjectButton: FC<{}> = () => { >
-

+

To transfer projects, the owner must be a member of both the source and target organizations.

@@ -189,29 +186,37 @@ const TransferProjectButton: FC<{}> = () => {
- {organizations.length > 0 && ( + {organizations && (
- setSelectedOrg(slug)} - placeholder="Select Organization" - > - - Select Organization - - {organizations.map((x: any) => ( - } - > - {x.name} + {organizations.length === 0 ? ( +
+ You do not have any organizations with an organization-level + subscription. +
+ ) : ( + setSelectedOrg(slug)} + placeholder="Select Organization" + > + + Select Organization - ))} - + {organizations.map((x: any) => ( + } + > + {x.name} + + ))} +
+ )} +

The target organization needs to use{' '} @@ -219,7 +224,7 @@ const TransferProjectButton: FC<{}> = () => { organization-level-billing - . If you're looking to migrate an organization to the new billing, head to your{' '} + . To migrate an organization to the new billing, head to your{' '} organizations settings @@ -287,7 +292,12 @@ const TransferProjectButton: FC<{}> = () => {

)} {transferPreviewData && transferPreviewData.warnings.length > 0 && ( - +
{transferPreviewData.warnings.map((warning) => (

{warning.message}

From e302638d2ae98263ec83a93d86e653cb1fe6f490 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Thu, 29 Jun 2023 13:28:12 +0200 Subject: [PATCH 11/12] feat: enable gotrue debug flag via localstorage --- package-lock.json | 8 ++++---- packages/common/gotrue.ts | 9 +++++++++ packages/common/package.json | 2 +- studio/lib/local-storage.ts | 1 + 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f7cf61213f..1b914615e5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42804,7 +42804,7 @@ "license": "MIT", "dependencies": { "@headlessui/react": "^1.7.14", - "@supabase/gotrue-js": "^2.31.0", + "@supabase/gotrue-js": "^2.35.0", "@supabase/ui": "^0.37.0-alpha.50", "react-use": "^17.4.0" }, @@ -42817,9 +42817,9 @@ } }, "packages/common/node_modules/@supabase/gotrue-js": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.31.0.tgz", - "integrity": "sha512-YcwlbbNfedlue/HVIXtYBb4fuOrs29gNOTl6AmyxPp4zryRxzFvslVN9kmLDBRUAVU9fnPJh2bgOR3chRjJX5w==", + "version": "2.35.0", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.35.0.tgz", + "integrity": "sha512-bLNIrBzFNRR61qUN8VZ4iV7q+fPilWyYQi0CZlxuHBiPtVcNPTul02DbNvgvw76hNz+mB5maay/Y17lSHnSRmw==", "dependencies": { "cross-fetch": "^3.1.5" } diff --git a/packages/common/gotrue.ts b/packages/common/gotrue.ts index 9df248b6197..2ef1061dfe7 100644 --- a/packages/common/gotrue.ts +++ b/packages/common/gotrue.ts @@ -1,9 +1,18 @@ import { GoTrueClient } from '@supabase/gotrue-js' export const STORAGE_KEY = process.env.NEXT_PUBLIC_STORAGE_KEY || 'supabase.dashboard.auth.token' +export const AUTH_DEBUG_KEY = + process.env.NEXT_PUBLIC_AUTH_DEBUG_KEY || 'supabase.dashboard.auth.debug' + +const debug = + process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && + globalThis && + globalThis.localStorage && + globalThis.localStorage.getItem(AUTH_DEBUG_KEY) === 'true' export const gotrueClient = new GoTrueClient({ url: process.env.NEXT_PUBLIC_GOTRUE_URL, storageKey: STORAGE_KEY, detectSessionInUrl: true, + debug, }) diff --git a/packages/common/package.json b/packages/common/package.json index eba24a4faeb..f41a62d834e 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -6,7 +6,7 @@ "license": "MIT", "dependencies": { "@headlessui/react": "^1.7.14", - "@supabase/gotrue-js": "^2.31.0", + "@supabase/gotrue-js": "^2.35.0", "@supabase/ui": "^0.37.0-alpha.50", "react-use": "^17.4.0" }, diff --git a/studio/lib/local-storage.ts b/studio/lib/local-storage.ts index d9747df8753..ce3d2b3f271 100644 --- a/studio/lib/local-storage.ts +++ b/studio/lib/local-storage.ts @@ -5,6 +5,7 @@ export const LOCAL_STORAGE_KEYS_ALLOWLIST = [ 'theme', 'supabaseDarkMode', 'supabase.dashboard.sign_in_clicks', + 'supabase.dashboard.auth.debug', ] export function clearLocalStorage() { From 05d229dae5458184c29f9328aa219f6372d55103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Thu, 29 Jun 2023 14:36:38 +0200 Subject: [PATCH 12/12] fix: conditional elements in Tabs --- packages/ui/src/components/Tabs/Tabs.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ui/src/components/Tabs/Tabs.tsx b/packages/ui/src/components/Tabs/Tabs.tsx index 67d389baecd..0ee08f3cc3a 100644 --- a/packages/ui/src/components/Tabs/Tabs.tsx +++ b/packages/ui/src/components/Tabs/Tabs.tsx @@ -54,6 +54,9 @@ const Tabs: React.FC & TabsSubComponents = ({ children = [children] } + // Exclude children that are just conditional elements + children = children.filter((it) => it) + const tabIds = children.map((tab) => tab.props.id) const { groupActiveId, setGroupActiveId } = useTabGroup(tabIds)