The dropdown is connecting APIs

This commit is contained in:
Matt Aitken
2022-12-28 13:53:40 +00:00
parent 4ffc94e9ee
commit 8d850c8c63
3 changed files with 97 additions and 31 deletions
@@ -1,9 +1,10 @@
import githubLogo from "~/assets/images/integrations/logo-github.png";
import { useEffect } from "react";
import { useCallback, useEffect } from "react";
import Pizzly from "@nangohq/pizzly-frontend";
import { useFetcher } from "@remix-run/react";
import type {
CreateResponse,
Update,
UpdateResponse,
} from "~/routes/api/v1/internal/connection";
@@ -26,15 +27,17 @@ const actionPath = "/api/v1/internal/connection";
export function ConnectButton({
integration,
organizationId,
sourceId,
className,
children,
}: {
integration: Integration;
organizationId: string;
sourceId?: string;
className?: string;
children: (status: Status) => React.ReactNode;
}) {
const { createFetcher, status } = useCreateConnection();
const { createFetcher, status } = useCreateConnection(sourceId);
return (
<createFetcher.Form method="post" action={actionPath}>
@@ -54,43 +57,87 @@ export function ConnectButton({
type Status = "loading" | "idle";
export function useCreateConnection() {
export function useCreateConnection(sourceId?: string) {
const createConnectionFetcher = useFetcher<CreateResponse>();
const completeConnectionFetcher = useFetcher<UpdateResponse>();
const status: Status =
createConnectionFetcher.state === "loading" ||
completeConnectionFetcher.state === "loading"
? "loading"
: "idle";
createConnectionFetcher.state === "idle" &&
completeConnectionFetcher.state === "idle"
? "idle"
: "loading";
useEffect(() => {
async function authenticationFlow() {
if (createConnectionFetcher.data === undefined) return;
const completeFlow = useCallback(
async ({
pizzlyHost,
connectionId,
service,
sourceId,
}: {
pizzlyHost: string;
connectionId: string;
service: string;
sourceId?: string;
}) => {
console.log(`completeFlow`, {
pizzlyHost,
connectionId,
service,
sourceId,
});
try {
const pizzly = new Pizzly(createConnectionFetcher.data.host);
const pizzly = new Pizzly(pizzlyHost);
await pizzly.auth(service, connectionId);
await pizzly.auth(
createConnectionFetcher.data.integrationKey,
createConnectionFetcher.data.connectionId
);
let completeData: Update = {
type: "update",
connectionId: connectionId,
};
completeConnectionFetcher.submit(
{
type: "update",
connectionId: createConnectionFetcher.data.connectionId,
},
{ method: "post", action: actionPath }
);
if (sourceId) {
completeData = {
...completeData,
sourceId,
};
}
completeConnectionFetcher.submit(completeData, {
method: "post",
action: actionPath,
});
} catch (error: any) {
console.error(
`There was an error in the OAuth flow for integration "${error.providerConfigKey}" and connection-id "${error.connectionId}": ${error.error.type} - ${error.error.message}`
`There was an error in the OAuth flow for integration "${error.providerConfigKey}" and connection-id "${error.connectionId}": ${error.error.type} - ${error.error.message}`,
error
);
}
}
},
[completeConnectionFetcher]
);
authenticationFlow();
}, [completeConnectionFetcher, createConnectionFetcher.data]);
useEffect(() => {
if (
createConnectionFetcher.state !== "idle" ||
createConnectionFetcher.type !== "done"
)
return;
if (completeConnectionFetcher.state !== "idle") return;
if (createConnectionFetcher.data === undefined) return;
completeFlow({
pizzlyHost: createConnectionFetcher.data.host,
connectionId: createConnectionFetcher.data.connectionId,
service: createConnectionFetcher.data.integrationKey,
sourceId,
});
}, [
completeConnectionFetcher,
completeConnectionFetcher.data,
completeConnectionFetcher.submit,
completeFlow,
createConnectionFetcher,
sourceId,
]);
return {
createFetcher: createConnectionFetcher,
@@ -35,6 +35,7 @@ export function ConnectionSelector({
key={integration.key}
integration={integration}
organizationId={organizationId}
sourceId={sourceId}
className="flex rounded-md bg-slate-800 border border-rose-400 gap-3 text-sm text-slate-200 items-center hover:bg-slate-800/30 transition shadow-md disabled:opacity-50 py-1 pl-1 pr-3"
>
{(status) => (
@@ -136,6 +137,7 @@ export function ConnectionSelector({
key={integration.key}
integration={integration}
organizationId={organizationId}
sourceId={sourceId}
>
{(status) => (
<div className="flex items-center gap-2 mx-1 pl-2.5 py-2 rounded hover:bg-slate-800 transition">
@@ -10,6 +10,16 @@ import { integrations } from "~/components/integrations/ConnectButton";
import { requireUserId } from "~/services/session.server";
import { APIConnectionType } from ".prisma/client";
import { env } from "~/env.server";
import { connectExternalSource } from "~/models/externalSource.server";
import { internalPubSub } from "~/services/messageBroker.server";
const updateSchema = z.object({
type: z.literal("update"),
connectionId: z.string(),
sourceId: z.string().optional(),
});
export type Update = z.infer<typeof updateSchema>;
const requestSchema = z.discriminatedUnion("type", [
z.object({
@@ -17,10 +27,7 @@ const requestSchema = z.discriminatedUnion("type", [
organizationId: z.string(),
key: z.string(),
}),
z.object({
type: z.literal("update"),
connectionId: z.string(),
}),
updateSchema,
]);
export type CreateResponse = {
@@ -85,17 +92,27 @@ export const action = async ({ request, params }: ActionArgs) => {
return json(response);
}
case "update": {
const { connectionId } = parsed;
const { connectionId, sourceId } = parsed;
await setConnectedAPIConnection({
id: connectionId,
});
console.log("sourceId", sourceId);
if (sourceId !== undefined) {
await connectExternalSource({ sourceId, connectionId });
await internalPubSub.publish("EXTERNAL_SOURCE_UPSERTED", {
id: sourceId,
});
}
return json({
success: true,
});
}
}
} catch (error: any) {
console.log("error", error);
return json({ message: error.message }, { status: 400 });
}
};