mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 23:29:08 +03:00
adds working project linker
This commit is contained in:
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,
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user