Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
pontusab committed Jul 23, 2024
1 parent 23e90de commit e04348e
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 171 deletions.
39 changes: 14 additions & 25 deletions apps/dashboard/src/actions/institutions/create-plaid-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,23 @@

import Midday from "@midday-ai/engine";
import { getSession } from "@midday/supabase/cached-queries";
import { z } from "zod";
import { authActionClient } from "../safe-action";

const engine = new Midday();

export const createPlaidLinkTokenAction = authActionClient
.schema(
z.object({
routingNumber: z.string().optional(),
}),
)
.action(async ({ parsedInput: { routingNumber } }) => {
console.log(routingNumber);
try {
const {
data: { session },
} = await getSession();
export const createPlaidLinkTokenAction = async () => {
try {
const {
data: { session },
} = await getSession();

const { data } = await engine.auth.plaid.link({
userId: session?.user.id,
routingNumber,
// institutionId
});
const { data } = await engine.auth.plaid.link({
userId: session?.user.id,
});

return data.link_token;
} catch (error) {
console.log(error);
return data.link_token;
} catch (error) {
console.log(error);

throw Error("Something went wrong.");
}
});
throw Error("Something went wrong.");
}
};
17 changes: 17 additions & 0 deletions apps/dashboard/src/actions/institutions/exchange-public-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use server";

import Midday from "@midday-ai/engine";

const engine = new Midday();

export const exchangePublicToken = async (token: string) => {
try {
const { data } = await engine.auth.plaid.exchange({ token });

return data.access_token;
} catch (error) {
console.log(error);

throw Error("Something went wrong.");
}
};
13 changes: 10 additions & 3 deletions apps/dashboard/src/components/bank-connect-button.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import { Button } from "@midday/ui/button";
import { Loader2 } from "lucide-react";
import { useState } from "react";

type Props = {
onClick: () => void;
isLoading: boolean;
};

export function BankConnectButton({ onClick, isLoading }: Props) {
export function BankConnectButton({ onClick }: Props) {
const [isLoading, setLoading] = useState(false);

const handleOnClick = () => {
setLoading(true);
onClick();
};

return (
<Button
variant="outline"
data-event="Bank Selected"
data-icon="🏦"
data-channel="bank"
disabled={isLoading}
onClick={onClick}
onClick={handleOnClick}
>
{isLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : "Connect"}
</Button>
Expand Down
6 changes: 3 additions & 3 deletions apps/dashboard/src/components/bank-logo.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Avatar, AvatarFallback, AvatarImage } from "@midday/ui/avatar";
import { Avatar, AvatarImage } from "@midday/ui/avatar";

type Props = {
src?: string;
src: string | null;
alt: string;
size?: number;
};
Expand All @@ -12,7 +12,7 @@ export function BankLogo({ src, alt, size = 34 }: Props) {
style={{ width: size, height: size }}
className="border border-border"
>
<AvatarImage src={src} alt={alt} />
{src && <AvatarImage src={src} alt={alt} />}
<AvatarImage src="https://cdn-engine.midday.ai/default.jpg" alt={alt} />
</Avatar>
);
Expand Down
38 changes: 25 additions & 13 deletions apps/dashboard/src/components/connect-bank-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
"use client";

import { updateInstitutionUsageAction } from "@/actions/institutions/update-institution-usage";
import { useConnectParams } from "@/hooks/use-connect-params";
import { useAction } from "next-safe-action/hooks";
import { PlaidConnect } from "./plaid-connect";
import { BankConnectButton } from "./bank-connect-button";
import { GoCardLessConnect } from "./gocardless-connect";
import { TellerConnect } from "./teller-connect";

type Props = {
id: string;
provider: string;
routingNumber?: string;
availableHistory: number;
countryCode: string;
openPlaid: () => void;
};

export function ConnectBankProvider({ id, provider, routingNumber }: Props) {
export function ConnectBankProvider({
id,
provider,
openPlaid,
availableHistory,
countryCode,
}: Props) {
const { setParams } = useConnectParams();
const updateInstitutionUsage = useAction(updateInstitutionUsageAction);

Expand All @@ -35,22 +42,27 @@ export function ConnectBankProvider({ id, provider, routingNumber }: Props) {
}}
/>
);
case "plaid": {
case "gocardless": {
return (
<PlaidConnect
<GoCardLessConnect
id={id}
routingNumber={routingNumber}
countryCode={countryCode}
availableHistory={availableHistory}
onSelect={() => {
setParams({ step: null });
updateUsage();
}}
/>
);
}

case "gocardless": {
return null;
}
case "plaid":
return (
<BankConnectButton
onClick={() => {
updateUsage();
openPlaid();
}}
/>
);
default:
return null;
}
Expand Down
33 changes: 33 additions & 0 deletions apps/dashboard/src/components/gocardless-connect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createGoCardLessLinkAction } from "@/actions/institutions/create-gocardless-link";
import { isDesktopApp } from "@todesktop/client-core/platform/todesktop";
import { useAction } from "next-safe-action/hooks";
import { BankConnectButton } from "./bank-connect-button";

type Props = {
id: string;
availableHistory: number;
countryCode: string;
onSelect: () => void;
};

export function GoCardLessConnect({
onSelect,
id,
availableHistory,
countryCode,
}: Props) {
const createGoCardLessLink = useAction(createGoCardLessLinkAction);

const handleOnSelect = () => {
onSelect();

createGoCardLessLink.execute({
institutionId: id,
availableHistory: availableHistory,
countryCode,
redirectBase: isDesktopApp() ? "midday://" : window.location.origin,
});
};

return <BankConnectButton onClick={handleOnSelect} />;
}
96 changes: 77 additions & 19 deletions apps/dashboard/src/components/modals/search-institutions-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"use client";

import { createPlaidLinkTokenAction } from "@/actions/institutions/create-plaid-link";
import { exchangePublicToken } from "@/actions/institutions/exchange-public-token";
import { getInstitutions } from "@/actions/institutions/get-institutions";
import { useConnectParams } from "@/hooks/use-connect-params";
import type { Institutions } from "@midday-ai/engine/resources/institutions/institutions";
import { track } from "@midday/events/client";
import { LogEvents } from "@midday/events/events";
import { Button } from "@midday/ui/button";
import {
Dialog,
Expand Down Expand Up @@ -63,19 +68,21 @@ function SearchSkeleton() {
type SearchResultProps = {
id: string;
name: string;
logo: string;
logo: string | null;
provider: string;
countryCode: string;
availableHistory: number;
routingNumber?: string;
openPlaid: () => void;
};

function SearchResult({
id,
name,
logo,
provider,
routingNumber,
availableHistory,
countryCode,
openPlaid,
}: SearchResultProps) {
return (
<div className="flex justify-between">
Expand All @@ -95,7 +102,9 @@ function SearchResult({
<ConnectBankProvider
id={id}
provider={provider}
routingNumber={routingNumber}
openPlaid={openPlaid}
availableHistory={availableHistory}
countryCode={countryCode}
/>
</div>
);
Expand All @@ -110,32 +119,55 @@ export function SearchInstitutionsModal({
}: SearchInstitutionsModalProps) {
const router = useRouter();
const [loading, setLoading] = useState(true);
const [results, setResults] = useState([]);
const [results, setResults] = useState<Institutions["data"]>([]);
const [plaidToken, setPlaidToken] = useState<string | undefined>();

const {
countryCode,
q: query,
step,
setParams,
} = useConnectParams(initialCountryCode);

const isOpen = step === "connect";
const debouncedSearchTerm = useDebounce(query, 100);

// NOTE: Load SDKs here so it's not unmonted
useScript("https://cdn.teller.io/connect/connect.js", {
removeOnUnmount: false,
});

usePlaidLink({
token: null,
const { open: openPlaid } = usePlaidLink({
token: plaidToken,
publicKey: "",
env: process.env.NEXT_PUBLIC_PLAID_ENVIRONMENT!,
clientName: "Midday",
product: ["transactions"],
onSuccess: async (public_token, metadata) => {},
onExit: () => {},
});
onSuccess: async (public_token, metadata) => {
const accessToken = await exchangePublicToken(public_token);

const {
countryCode,
q: query,
step,
setParams,
} = useConnectParams(initialCountryCode);
setParams({
step: "account",
provider: "plaid",
token: accessToken,
institution_id: metadata.institution?.institution_id,
});
track({
event: LogEvents.ConnectBankAuthorized.name,
channel: LogEvents.ConnectBankAuthorized.channel,
provider: "plaid",
});
},
onExit: () => {
setParams({ step: "connect" });

const isOpen = step === "connect";
const debouncedSearchTerm = useDebounce(query, 100);
track({
event: LogEvents.ConnectBankCanceled.name,
channel: LogEvents.ConnectBankCanceled.channel,
provider: "plaid",
});
},
});

const handleOnClose = () => {
setParams({
Expand Down Expand Up @@ -173,6 +205,20 @@ export function SearchInstitutionsModal({
}
}, [debouncedSearchTerm, isOpen]);

useEffect(() => {
async function createLinkToken() {
const token = await createPlaidLinkTokenAction();

if (token) {
setPlaidToken(token);
}
}

if (isOpen) {
createLinkToken();
}
}, [isOpen]);

return (
<Dialog open={isOpen} onOpenChange={handleOnClose}>
<DialogContent>
Expand Down Expand Up @@ -210,6 +256,10 @@ export function SearchInstitutionsModal({
{loading && <SearchSkeleton />}

{results?.map((institution) => {
if (!institution) {
return null;
}

return (
<SearchResult
key={institution.id}
Expand All @@ -218,7 +268,15 @@ export function SearchInstitutionsModal({
logo={institution.logo}
provider={institution.provider}
countryCode={countryCode}
routingNumber={institution?.routing_numbers?.at(0)}
availableHistory={
institution.available_history
? +institution.available_history
: 0
}
openPlaid={() => {
setParams({ step: null });
openPlaid();
}}
/>
);
})}
Expand Down
Loading

0 comments on commit e04348e

Please sign in to comment.