diff --git a/apps/dashboard/src/components/connect-bank-provider.tsx b/apps/dashboard/src/components/connect-bank-provider.tsx index 905c5dd4d0..7bbdc46b62 100644 --- a/apps/dashboard/src/components/connect-bank-provider.tsx +++ b/apps/dashboard/src/components/connect-bank-provider.tsx @@ -3,7 +3,6 @@ import { useConnectParams } from "@/hooks/use-connect-params"; import { useAction } from "next-safe-action/hooks"; import { BankConnectButton } from "./bank-connect-button"; import { GoCardLessConnect } from "./gocardless-connect"; -import { PluggyConnect } from "./pluggy-connect"; import { TellerConnect } from "./teller-connect"; type Props = { diff --git a/apps/dashboard/src/components/modals/connect-transactions-modal.tsx b/apps/dashboard/src/components/modals/connect-transactions-modal.tsx index 0aad2ffceb..75d42f4bd6 100644 --- a/apps/dashboard/src/components/modals/connect-transactions-modal.tsx +++ b/apps/dashboard/src/components/modals/connect-transactions-modal.tsx @@ -20,6 +20,7 @@ import { import { Input } from "@midday/ui/input"; import { Skeleton } from "@midday/ui/skeleton"; import { useDebounce, useScript } from "@uidotdev/usehooks"; +import { useTheme } from "next-themes"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { usePlaidLink } from "react-plaid-link"; @@ -97,6 +98,7 @@ export function ConnectTransactionsModal({ countryCode: initialCountryCode, }: ConnectTransactionsModalProps) { const router = useRouter(); + const { theme } = useTheme(); const [loading, setLoading] = useState(true); const [results, setResults] = useState([]); const [plaidToken, setPlaidToken] = useState(); @@ -152,6 +154,8 @@ export function ConnectTransactionsModal({ const { open: openPluggy } = usePluggyLink({ token: pluggyToken, + theme: theme === "dark" ? "dark" : "light", + language: "en", onSuccess: (itemData) => { // const { access_token, item_id } = await exchangePublicToken(public_token); diff --git a/apps/dashboard/src/hooks/use-pluggy-link.ts b/apps/dashboard/src/hooks/use-pluggy-link.ts index 44f4bba45e..2b30e74ce2 100644 --- a/apps/dashboard/src/hooks/use-pluggy-link.ts +++ b/apps/dashboard/src/hooks/use-pluggy-link.ts @@ -8,6 +8,8 @@ type Props = { onSuccess: (itemData: any) => void; onError: () => void; includeSandbox?: boolean; + theme?: "light" | "dark"; + language?: string; }; export function usePluggyLink({ @@ -17,6 +19,8 @@ export function usePluggyLink({ onSuccess, onError, includeSandbox = false, + language = "pt", + theme, }: Props) { const [connectToken, setConnectToken] = useState(""); const [selectedConnectorId, setSelectedConnectorId] = useState< @@ -34,6 +38,8 @@ export function usePluggyLink({ onClose: onExit, onSuccess, onError, + theme, + language, }); const handleOpen = ({ institutionId }: { institutionId?: string }) => { diff --git a/apps/engine/src/providers/pluggy/pluggy-api.ts b/apps/engine/src/providers/pluggy/pluggy-api.ts index d024586eed..1af79ff09e 100644 --- a/apps/engine/src/providers/pluggy/pluggy-api.ts +++ b/apps/engine/src/providers/pluggy/pluggy-api.ts @@ -10,6 +10,7 @@ import type { ConnectTokenResponse, GetAccountsRequest, GetInstitutionsRequest, + GetInstitutionsResponse, GetStatusResponse, GetTransactionsParams, LinkTokenCreateRequest, @@ -109,9 +110,15 @@ export class PluggyApi { }; } - async getInstitutions({ countries }: GetInstitutionsRequest) { - const response = await this.#get("/connectors", undefined, { - countries: countries, + async getInstitutions({ + countries, + sandbox, + }: GetInstitutionsRequest): Promise { + const apiKey = await this.#getApiKey(); + + const response = await this.#get("/connectors", apiKey, { + countries, + sandbox: sandbox ? "true" : "false", }); return response.results; diff --git a/apps/engine/src/providers/pluggy/types.ts b/apps/engine/src/providers/pluggy/types.ts index 11fb79ae55..819816fc4f 100644 --- a/apps/engine/src/providers/pluggy/types.ts +++ b/apps/engine/src/providers/pluggy/types.ts @@ -19,6 +19,7 @@ export type GetStatusResponse = { export type GetInstitutionsRequest = { countries?: string[]; + sandbox?: boolean; }; export type GetAccountsRequest = { @@ -34,3 +35,95 @@ export type LinkTokenCreateRequest = { export type ConnectTokenResponse = { accessToken: string; }; + +export const CREDENTIAL_TYPES = [ + "number", + "password", + "text", + "image", + "select", + "ethaddress", + "hcaptcha", +] as const; + +export const CONNECTOR_TYPES = [ + "PERSONAL_BANK", + "BUSINESS_BANK", + "INVOICE", + "INVESTMENT", + "TELECOMMUNICATION", + "DIGITAL_ECONOMY", + "PAYMENT_ACCOUNT", + "OTHER", +] as const; +/** + * @typedef ConnectorType + * Type of connectors available + */ +export type ConnectorType = (typeof CONNECTOR_TYPES)[number]; + +export type CredentialSelectOption = { + /** Value of the option */ + value: string; + /** Displayable text or label of the option */ + label: string; +}; + +export type CredentialType = (typeof CREDENTIAL_TYPES)[number]; + +export type ConnectorCredential = { + /** parameter label that describes it */ + label: string; + /** parameter key name */ + name: string; + /** type of parameter to create the form */ + type?: CredentialType; + /** If parameter is used for MFA. */ + mfa?: boolean; + /** If parameter is image, base64 string is provided */ + data?: string; + /** Assistive information to help the user provide us the credential */ + assistiveText?: string; + /** Available options if credential is of type 'select' */ + options?: CredentialSelectOption[]; + /** Regex to validate input */ + validation?: string; + /** Error message of input validation on institution language */ + validationMessage?: string; + /** Input's placeholder for help */ + placeholder?: string; + /** Is this credential optional? */ + optional?: boolean; + /** Applies to MFA credential only - Detailed information that includes details/hints that the user should be aware of */ + instructions?: string; + /** Parameter expiration date, input value should be submitted before this date. */ + expiresAt?: Date; +}; + +export type Connector = { + id: number; + /** Financial institution name */ + name: string; + /** Url of the institution that the connector represents */ + institutionUrl: string; + /** Image url of the institution. */ + imageUrl: string; + /** Primary color of the institution */ + primaryColor: string; + /** Type of the connector */ + type: ConnectorType; + /** Country of the institution */ + country: string; + /** List of parameters needed to execute the connector */ + credentials: ConnectorCredential[]; + /** Has MFA steps */ + hasMFA: boolean; + /** If true, connector has an Oauth login */ + oauth?: boolean; + /** (only for OAuth connector) this URL is used to connect the user and on success it will redirect to create the new item */ + oauthUrl?: string; +}; + +export type GetInstitutionsResponse = { + results: Connector[]; +}; diff --git a/apps/engine/tasks/download-pluggy.ts b/apps/engine/tasks/download-pluggy.ts index 8869ec146a..5ea5d427f1 100644 --- a/apps/engine/tasks/download-pluggy.ts +++ b/apps/engine/tasks/download-pluggy.ts @@ -11,7 +11,9 @@ async function main() { }, }); - const data = await provider.getInstitutions({ countries: ["BR"] }); + const data = await provider.getInstitutions({ + countries: ["BR"], + }); const tasks = data.map(async (institution) => { const extension = getFileExtension(institution.imageUrl); diff --git a/apps/engine/tasks/get-institutions.ts b/apps/engine/tasks/get-institutions.ts index 2eb480c0bc..6e2022ceb2 100644 --- a/apps/engine/tasks/get-institutions.ts +++ b/apps/engine/tasks/get-institutions.ts @@ -77,7 +77,9 @@ export async function getPluggyInstitutions() { }, }); - const data = await provider.getInstitutions({ countries: ["BR"] }); + const data = await provider.getInstitutions({ + countries: ["BR"], + }); return data.map((institution) => { const extension = getFileExtension(institution.imageUrl);