adds working project linker

This commit is contained in:
Alaister Young committed 2023-06-14 00:29:48 +10:00
1 parent de6f88296c
commit d5fa607a8a
3 files changed
+184 -39

No files matched your search

@@ -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 (
<div>
<ProjectLinkerItem foreignProjects={foreignProjects} supabaseProjects={supabaseProjects} />
<div className="flex flex-col gap-4">
<ul>
{connections.map((connection, i) => (
<ProjectLinkerItem
key={i}
foreignProjects={foreignProjects}
supabaseProjects={supabaseProjects}
foreignProjectId={connection.foreignProjectId}
setForeignProjectId={(id) => onSetForeignProjectId(i, id)}
supabaseProjectId={connection.supabaseProjectId}
setSupabaseProjectId={(id) => onSetSupabaseProjectId(i, id)}
removeConnection={() => removeConnection(i)}
canRemove={connections.length > 1}
/>
))}
</ul>
<Button
onClick={addConnection}
type="default"
icon={<IconPlus strokeWidth={2} />}
className="self-start"
>
Add Connection
</Button>
<hr className="h-px bg-scale-500" role="separator" />
<Button
className="self-end"
onClick={onCreateConnections}
loading={isLoading}
disabled={isLoading}
>
Continue
</Button>
</div>
)
}
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<string>(UNDEFINED_SELECT_VALUE)
const [supabaseProjectId, setSupabaseProjectId] = useState<string>(UNDEFINED_SELECT_VALUE)
const ProjectLinkerItem = ({
foreignProjects,
supabaseProjects,
foreignProjectId,
setForeignProjectId,
supabaseProjectId,
setSupabaseProjectId,
removeConnection,
canRemove,
}: ProjectLinkerItemProps) => {
return (
<li className="py-2">
<div className="relative flex w-full space-x-2">
@@ -44,15 +140,7 @@ const ProjectLinkerItem = ({ foreignProjects, supabaseProjects }: ProjectLinkerI
<Listbox.Option value={UNDEFINED_SELECT_VALUE} label="Choose a project" disabled>
Choose a project
</Listbox.Option>
{/* {selectedForeignProject && (
<Listbox.Option
value={selectedForeignProject.id}
label={selectedForeignProject.name}
addOnBefore={() => vercelIcon}
>
{selectedForeignProject.name}
</Listbox.Option>
)} */}
{foreignProjects.map((project) => (
<Listbox.Option
key={project.id}
@@ -66,7 +154,7 @@ const ProjectLinkerItem = ({ foreignProjects, supabaseProjects }: ProjectLinkerI
</Listbox>
</div>
<div className="flex flex-shrink items-center">
<IconChevronRight className="text-scale-1000" />
<IconArrowRight className="text-scale-1000" />
</div>
<div className="w-1/2 flex-grow">
<Listbox
@@ -76,6 +164,7 @@ const ProjectLinkerItem = ({ foreignProjects, supabaseProjects }: ProjectLinkerI
<Listbox.Option value={UNDEFINED_SELECT_VALUE} label="Choose a project" disabled>
Choose a project
</Listbox.Option>
{supabaseProjects.map((project) => (
<Listbox.Option
key={project.id}
@@ -88,16 +177,10 @@ const ProjectLinkerItem = ({ foreignProjects, supabaseProjects }: ProjectLinkerI
))}
</Listbox>
</div>
{/* {idx != 0 && (
<div className="absolute top-[3px] right-[-50px]">
<Button
type="text"
icon={<IconX size="small" strokeWidth={2} />}
onClick={onRemove}
disabled={_store.waitingIntegration}
/>
</div>
)} */}
{canRemove && (
<Button type="text" icon={<IconX strokeWidth={2} />} onClick={removeConnection} />
)}
</div>
</li>
)
@@ -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<ReturnType<typeof createIntegrationConnections>>
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,
})
}
+20 -9
View File
@@ -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<Organization | null>(null)
const [hasInstalled, setHasInstalled] = useState(true)
const [organizationIntegrationId, setOrganizationIntegrationId] = useState<string | null>(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 = () => {
<div className="flex flex-col gap-4">
<h1>Vercel Integration</h1>
{!hasInstalled ? (
{organizationIntegrationId === null ? (
<>
<OrganizationPicker onSelectedOrgChange={setSelectedOrg} />
@@ -84,7 +86,16 @@ const VercelIntegration: NextPageWithLayout = () => {
</>
) : (
<>
<ProjectLinker foreignProjects={vercelProjects} supabaseProjects={supabaseProjects} />
<ProjectLinker
organizationIntegrationId={organizationIntegrationId}
foreignProjects={vercelProjects}
supabaseProjects={supabaseProjects}
onCreateConnections={() => {
if (next) {
window.location.href = next
}
}}
/>
</>
)}
</div>