From d5fa607a8a01844598aceb2ea8141e02dac05acc Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Wed, 14 Jun 2023 00:29:48 +1000 Subject: [PATCH] adds working project linker --- .../interfaces/Integrations/ProjectLinker.tsx | 143 ++++++++++++++---- ...integration-connections-create-mutation.ts | 51 +++++++ studio/pages/integrations/vercel.tsx | 29 ++-- 3 files changed, 184 insertions(+), 39 deletions(-) create mode 100644 studio/data/integrations/integration-connections-create-mutation.ts diff --git a/studio/components/interfaces/Integrations/ProjectLinker.tsx b/studio/components/interfaces/Integrations/ProjectLinker.tsx index aa1fce0b4f1..05a4b10d94f 100644 --- a/studio/components/interfaces/Integrations/ProjectLinker.tsx +++ b/studio/components/interfaces/Integrations/ProjectLinker.tsx @@ -1,6 +1,7 @@ import { databaseIcon, vercelIcon } from 'components/to-be-cleaned/ListIcons' +import { useIntegrationConnectionsCreateMutation } from 'data/integrations/integration-connections-create-mutation' import { useState } from 'react' -import { IconChevronRight, Listbox } from 'ui' +import { Button, IconArrowRight, IconPlus, IconX, Listbox } from 'ui' interface Project { id: string @@ -8,31 +9,126 @@ interface Project { } export interface ProjectLinkerProps { + organizationIntegrationId: string foreignProjects: Project[] supabaseProjects: Project[] + onCreateConnections?: () => void } -const ProjectLinker = ({ foreignProjects, supabaseProjects }: ProjectLinkerProps) => { +const UNDEFINED_SELECT_VALUE = 'undefined' + +const ProjectLinker = ({ + organizationIntegrationId, + foreignProjects, + supabaseProjects, + onCreateConnections: _onCreateConnections, +}: ProjectLinkerProps) => { + const [connections, setConnections] = useState([ + { foreignProjectId: UNDEFINED_SELECT_VALUE, supabaseProjectId: UNDEFINED_SELECT_VALUE }, + ]) + + function onSetForeignProjectId(idx: number, id: string) { + const newConnections = [...connections] + newConnections[idx].foreignProjectId = id + setConnections(newConnections) + } + + function onSetSupabaseProjectId(idx: number, id: string) { + const newConnections = [...connections] + newConnections[idx].supabaseProjectId = id + setConnections(newConnections) + } + + function addConnection() { + setConnections([ + ...connections, + { foreignProjectId: UNDEFINED_SELECT_VALUE, supabaseProjectId: UNDEFINED_SELECT_VALUE }, + ]) + } + + function removeConnection(idx: number) { + const newConnections = [...connections] + newConnections.splice(idx, 1) + setConnections(newConnections) + } + + const { mutate: createConnections, isLoading } = useIntegrationConnectionsCreateMutation({ + onSuccess() { + _onCreateConnections?.() + }, + }) + + function onCreateConnections() { + createConnections({ + organizationIntegrationId, + connections, + }) + } + return ( -
- +
+
    + {connections.map((connection, i) => ( + onSetForeignProjectId(i, id)} + supabaseProjectId={connection.supabaseProjectId} + setSupabaseProjectId={(id) => onSetSupabaseProjectId(i, id)} + removeConnection={() => removeConnection(i)} + canRemove={connections.length > 1} + /> + ))} +
+ + + +
+ +
) } export default ProjectLinker -const UNDEFINED_SELECT_VALUE = 'undefined' - interface ProjectLinkerItemProps { foreignProjects: Project[] supabaseProjects: Project[] + foreignProjectId: string + setForeignProjectId: (id: string) => void + supabaseProjectId: string + setSupabaseProjectId: (id: string) => void + removeConnection: () => void + canRemove: boolean } -const ProjectLinkerItem = ({ foreignProjects, supabaseProjects }: ProjectLinkerItemProps) => { - const [foreignProjectId, setForeignProjectId] = useState(UNDEFINED_SELECT_VALUE) - const [supabaseProjectId, setSupabaseProjectId] = useState(UNDEFINED_SELECT_VALUE) - +const ProjectLinkerItem = ({ + foreignProjects, + supabaseProjects, + foreignProjectId, + setForeignProjectId, + supabaseProjectId, + setSupabaseProjectId, + removeConnection, + canRemove, +}: ProjectLinkerItemProps) => { return (
  • @@ -44,15 +140,7 @@ const ProjectLinkerItem = ({ foreignProjects, supabaseProjects }: ProjectLinkerI Choose a project - {/* {selectedForeignProject && ( - vercelIcon} - > - {selectedForeignProject.name} - - )} */} + {foreignProjects.map((project) => (
    - +
    Choose a project + {supabaseProjects.map((project) => (
    - {/* {idx != 0 && ( -
    -
    - )} */} + + {canRemove && ( +
  • ) diff --git a/studio/data/integrations/integration-connections-create-mutation.ts b/studio/data/integrations/integration-connections-create-mutation.ts new file mode 100644 index 00000000000..82ffa0d8b27 --- /dev/null +++ b/studio/data/integrations/integration-connections-create-mutation.ts @@ -0,0 +1,51 @@ +import { useMutation, UseMutationOptions } from '@tanstack/react-query' +import { post } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' + +export type IntegrationConnectionsCreateVariables = { + organizationIntegrationId: string + connections: { + foreignProjectId: string + supabaseProjectId: string + }[] +} + +export async function createIntegrationConnections({ + organizationIntegrationId, + connections, +}: IntegrationConnectionsCreateVariables) { + const response = await post(`${API_URL}/integrations/connections`, { + organizationIntegrationId, + connections, + }) + if (response.error) { + throw response.error + } + + return response +} + +type IntegrationConnectionsCreateData = Awaited> + +export const useIntegrationConnectionsCreateMutation = ({ + onSuccess, + ...options +}: Omit< + UseMutationOptions< + IntegrationConnectionsCreateData, + unknown, + IntegrationConnectionsCreateVariables + >, + 'mutationFn' +> = {}) => { + return useMutation< + IntegrationConnectionsCreateData, + unknown, + IntegrationConnectionsCreateVariables + >((vars) => createIntegrationConnections(vars), { + async onSuccess(data, variables, context) { + await onSuccess?.(data, variables, context) + }, + ...options, + }) +} diff --git a/studio/pages/integrations/vercel.tsx b/studio/pages/integrations/vercel.tsx index bb060a236fe..8d0e77d637a 100644 --- a/studio/pages/integrations/vercel.tsx +++ b/studio/pages/integrations/vercel.tsx @@ -14,13 +14,13 @@ import { Button } from 'ui' const VercelIntegration: NextPageWithLayout = () => { const { ui } = useStore() - const { code, configurationId } = useParams() + const { code, configurationId, next } = useParams() const [selectedOrg, setSelectedOrg] = useState(null) - const [hasInstalled, setHasInstalled] = useState(true) + const [organizationIntegrationId, setOrganizationIntegrationId] = useState(null) const { mutate, isLoading } = useVercelIntegrationCreateMutation({ - onSuccess() { - setHasInstalled(true) + onSuccess({ id }) { + setOrganizationIntegrationId(id) }, }) @@ -45,12 +45,14 @@ const VercelIntegration: NextPageWithLayout = () => { }) } - const { data: supabaseProjectsData } = useProjectsQuery({ enabled: hasInstalled }) + const { data: supabaseProjectsData } = useProjectsQuery({ + enabled: organizationIntegrationId !== null, + }) const supabaseProjects = useMemo( () => supabaseProjectsData ?.filter((project) => project.organization_id === selectedOrg?.id) - .map((project) => ({ id: project.ref, name: project.name })) ?? EMPTY_ARR, + .map((project) => ({ id: project.id.toString(), name: project.name })) ?? EMPTY_ARR, [selectedOrg?.id, supabaseProjectsData] ) @@ -59,7 +61,7 @@ const VercelIntegration: NextPageWithLayout = () => { orgId: selectedOrg?.id, orgSlug: selectedOrg?.slug, }, - { enabled: hasInstalled } + { enabled: organizationIntegrationId !== null } ) const vercelProjects = useMemo( () => @@ -74,7 +76,7 @@ const VercelIntegration: NextPageWithLayout = () => {

    Vercel Integration

    - {!hasInstalled ? ( + {organizationIntegrationId === null ? ( <> @@ -84,7 +86,16 @@ const VercelIntegration: NextPageWithLayout = () => { ) : ( <> - + { + if (next) { + window.location.href = next + } + }} + /> )}