mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat(webapp): popup Plaid Link component
This commit is contained in:
121
packages/webapp/src/containers/Banking/Plaid/PlaidLanchLink.tsx
Normal file
121
packages/webapp/src/containers/Banking/Plaid/PlaidLanchLink.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
usePlaidLink,
|
||||
PlaidLinkOnSuccessMetadata,
|
||||
PlaidLinkOnExitMetadata,
|
||||
PlaidLinkError,
|
||||
PlaidLinkOptionsWithLinkToken,
|
||||
PlaidLinkOnEventMetadata,
|
||||
PlaidLinkStableEvent,
|
||||
} from 'react-plaid-link';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
// import { exchangeToken, setItemState } from '../services/api';
|
||||
// import { useItems, useLink, useErrors } from '../services';
|
||||
|
||||
interface LaunchLinkProps {
|
||||
isOauth?: boolean;
|
||||
token: string;
|
||||
userId: number;
|
||||
itemId?: number | null;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
// Uses the usePlaidLink hook to manage the Plaid Link creation. See https://github.com/plaid/react-plaid-link for full usage instructions.
|
||||
// The link token passed to usePlaidLink cannot be null. It must be generated outside of this component. In this sample app, the link token
|
||||
// is generated in the link context in client/src/services/link.js.
|
||||
|
||||
export function LaunchLink(props: LaunchLinkProps) {
|
||||
const history = useHistory();
|
||||
// const { getItemsByUser, getItemById } = useItems();
|
||||
// const { generateLinkToken, deleteLinkToken } = useLink();
|
||||
// const { setError, resetError } = useErrors();
|
||||
|
||||
// define onSuccess, onExit and onEvent functions as configs for Plaid Link creation
|
||||
const onSuccess = async (
|
||||
publicToken: string,
|
||||
metadata: PlaidLinkOnSuccessMetadata,
|
||||
) => {
|
||||
// log and save metatdata
|
||||
// logSuccess(metadata, props.userId);
|
||||
if (props.itemId != null) {
|
||||
// update mode: no need to exchange public token
|
||||
// await setItemState(props.itemId, 'good');
|
||||
// deleteLinkToken(null, props.itemId);
|
||||
// getItemById(props.itemId, true);
|
||||
// regular link mode: exchange public token for access token
|
||||
} else {
|
||||
// call to Plaid api endpoint: /item/public_token/exchange in order to obtain access_token which is then stored with the created item
|
||||
// await exchangeToken(
|
||||
// publicToken,
|
||||
// metadata.institution,
|
||||
// metadata.accounts,
|
||||
// props.userId,
|
||||
// );
|
||||
// getItemsByUser(props.userId, true);
|
||||
}
|
||||
// resetError();
|
||||
// deleteLinkToken(props.userId, null);
|
||||
history.push(`/user/${props.userId}`);
|
||||
};
|
||||
|
||||
const onExit = async (
|
||||
error: PlaidLinkError | null,
|
||||
metadata: PlaidLinkOnExitMetadata,
|
||||
) => {
|
||||
// log and save error and metatdata
|
||||
// logExit(error, metadata, props.userId);
|
||||
if (error != null && error.error_code === 'INVALID_LINK_TOKEN') {
|
||||
// await generateLinkToken(props.userId, props.itemId);
|
||||
}
|
||||
if (error != null) {
|
||||
// setError(error.error_code, error.display_message || error.error_message);
|
||||
}
|
||||
// to handle other error codes, see https://plaid.com/docs/errors/
|
||||
};
|
||||
|
||||
const onEvent = async (
|
||||
eventName: PlaidLinkStableEvent | string,
|
||||
metadata: PlaidLinkOnEventMetadata,
|
||||
) => {
|
||||
// handle errors in the event end-user does not exit with onExit function error enabled.
|
||||
if (eventName === 'ERROR' && metadata.error_code != null) {
|
||||
// setError(metadata.error_code, ' ');
|
||||
}
|
||||
// logEvent(eventName, metadata);
|
||||
};
|
||||
|
||||
const config: PlaidLinkOptionsWithLinkToken = {
|
||||
onSuccess,
|
||||
onExit,
|
||||
onEvent,
|
||||
token: props.token,
|
||||
};
|
||||
|
||||
if (props.isOauth) {
|
||||
// add additional receivedRedirectUri config when handling an OAuth reidrect
|
||||
config.receivedRedirectUri = window.location.href;
|
||||
}
|
||||
const { open, ready } = usePlaidLink(config);
|
||||
|
||||
useEffect(() => {
|
||||
// initiallizes Link automatically
|
||||
if (props.isOauth && ready) {
|
||||
open();
|
||||
} else if (ready) {
|
||||
// regular, non-OAuth case:
|
||||
// set link token, userId and itemId in local storage for use if needed later by OAuth
|
||||
|
||||
localStorage.setItem(
|
||||
'oauthConfig',
|
||||
JSON.stringify({
|
||||
userId: props.userId,
|
||||
itemId: props.itemId,
|
||||
token: props.token,
|
||||
}),
|
||||
);
|
||||
open();
|
||||
}
|
||||
}, [ready, open, props.isOauth, props.userId, props.itemId, props.token]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
@@ -14,7 +14,10 @@ import {
|
||||
Icon,
|
||||
FormattedMessage as T,
|
||||
} from '@/components';
|
||||
import { useRefreshCashflowAccounts } from '@/hooks/query';
|
||||
import {
|
||||
useGetPlaidLinkToken,
|
||||
useRefreshCashflowAccounts,
|
||||
} from '@/hooks/query';
|
||||
import { CashflowAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
@@ -26,6 +29,7 @@ import { ACCOUNT_TYPE } from '@/constants';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { LaunchLink } from '@/containers/Banking/Plaid/PlaidLanchLink';
|
||||
|
||||
/**
|
||||
* Cash Flow accounts actions bar.
|
||||
@@ -63,8 +67,20 @@ function CashFlowAccountsActionsBar({
|
||||
setCashflowAccountsTableState({ inactiveMode: checked });
|
||||
};
|
||||
|
||||
const { mutateAsync: getPlaidLinkToken } = useGetPlaidLinkToken();
|
||||
const [linkToken, setLinkToken] = useState<string>('');
|
||||
|
||||
const handleConnectToBank = () => {
|
||||
getPlaidLinkToken()
|
||||
.then((res) => {
|
||||
setLinkToken(res.data.link_token);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<LaunchLink userId={3} token={linkToken} />
|
||||
<NavbarGroup>
|
||||
<Can I={CashflowAction.Create} a={AbilitySubject.Cashflow}>
|
||||
<Button
|
||||
@@ -104,6 +120,12 @@ function CashFlowAccountsActionsBar({
|
||||
onChange={handleInactiveSwitchChange}
|
||||
/>
|
||||
</Can>
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
text={'Connect to Bank'}
|
||||
onClick={handleConnectToBank}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
|
||||
<NavbarGroup align={Alignment.RIGHT}>
|
||||
|
||||
@@ -37,3 +37,4 @@ export * from './transactionsLocking';
|
||||
export * from './warehouses';
|
||||
export * from './branches';
|
||||
export * from './warehousesTransfers';
|
||||
export * from './plaid';
|
||||
17
packages/webapp/src/hooks/query/plaid.ts
Normal file
17
packages/webapp/src/hooks/query/plaid.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// @ts-nocheck
|
||||
import { useMutation } from 'react-query';
|
||||
import useApiRequest from '../useRequest';
|
||||
|
||||
/**
|
||||
* Retrieves the plaid link token.
|
||||
*/
|
||||
export function useGetPlaidLinkToken(props) {
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
() => apiRequest.post('banking/plaid/link-token', {}, {}),
|
||||
{
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user