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}
+ />
+ ))}
+
+
+
}
+ className="self-start"
+ >
+ Add Connection
+
+
+
+
+
)
}
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 && (
-
- }
- onClick={onRemove}
- disabled={_store.waitingIntegration}
- />
-
- )} */}
+
+ {canRemove && (
+ } onClick={removeConnection} />
+ )}
)
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
+ }
+ }}
+ />
>
)}