Skip to content

Commit

Permalink
Update to v1.0.3 (#4)
Browse files Browse the repository at this point in the history
* Removed the default user account.
* Improved documentation.
* Added some logging of Plaid API requests to store request identifiers (to align with the [Plaid docs][plaid-docs]).
* Removed the Plaid item id from the client (to align with the [Plaid docs][plaid-docs]).
* Removed the Beta label.

[plaid-docs]: https://plaid.com/docs/#storing-plaid-api-identifiers
  • Loading branch information
evndean authored Jul 23, 2019
1 parent bef1c33 commit 0cd1311
Show file tree
Hide file tree
Showing 35 changed files with 623 additions and 277 deletions.
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Plaid Pattern (Beta)
# Plaid Pattern

![Plaid Pattern client][client-img]

Expand All @@ -8,7 +8,7 @@ This is a reference application demonstrating an end-to-end [Plaid][plaid] integ

## Requirements

- [Docker][docker] Version 2.0.0.3 (31259) or higher, installed and running
- [Docker][docker] Version 2.0.0.3 (31259) or higher, installed, running, and signed in
- [Plaid API keys][plaid-keys] - [sign up][plaid-signup] for a free Sandbox account if you don't already have one

## Getting Started
Expand Down Expand Up @@ -38,8 +38,16 @@ This is a reference application demonstrating an end-to-end [Plaid][plaid] integ
All available commands can be seen by calling `make help`.
## Architecture
As a modern full-stack application, Pattern consists of multiple services handling different segments of the stack:
For more information about the individual services, see the readmes for the [client][client-readme], [database][database-readme], and [server][server-readme].
- [`database`][database-readme] runs a [PostgreSQL][postgres] database
- [`server`][server-readme] runs an application back-end server using [NodeJS] and [Express]
- [`client`][client-readme] runs a [React]-based single-page web frontend
- [`ngrok`][ngrok-readme] exposes a [ngrok] tunnel from your local machine to the Internet to receive webhooks
We use [Docker Compose][docker-compose] to orchestrate these services. As such, each individual service has its own Dockerfile, which Docker Compose reads when bringing up the services.
For more information about the individual services, see their readmes, linked in the list above.
## Troubleshooting
Expand All @@ -59,13 +67,20 @@ See [`docs/troubleshooting.md`][troubleshooting].
[client-img]: docs/pattern_screenshot.png
[client-readme]: client/README.md
[database-readme]: database/README.md
[docker]: https://www.docker.com/products/docker-desktop
[docker]: https://docs.docker.com/
[docker-compose]: https://docs.docker.com/compose/
[express]: https://expressjs.com/
[ngrok]: https://ngrok.com/
[ngrok-readme]: ngrok/README.md
[nodejs]: https://nodejs.org/en/
[plaid]: https://plaid.com
[plaid-docs]: https://plaid.com/docs/
[plaid-help]: https://support.plaid.com/hc/en-us
[plaid-keys]: https://dashboard.plaid.com/account/keys
[plaid-quickstart]: https://plaid.com/docs/quickstart/
[plaid-signup]: https://dashboard.plaid.com/signup
[plaid-support-ticket]: https://dashboard.plaid.com/support/new
[postgres]: https://www.postgresql.org/
[react]: http://reactjs.org/
[server-readme]: server/README.md
[troubleshooting]: docs/troubleshooting.md
18 changes: 14 additions & 4 deletions client/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# Plaid Pattern - Client

The client-side code demonstrates a [Plaid Link](https://plaid.com/docs/#integrating-with-link) integration. It is written using [React](https://reactjs.org/) (bootstrapped with [Create React App](https://github.com/facebook/create-react-app)). The app runs on port 3000 by default, although you can modify this in [docker-compose.yml](../docker-compose.yml).
The Pattern web client is written in JavaScript using [React]. It presents a basic [Link][plaid-link] workflow to the user, as well as a simple dashboard displaying linked accounts and transactions. The app runs on port 3000 by default, although you can modify this in [docker-compose.yml](../docker-compose.yml).

## Learn More
## Key concepts

- [React documentation](https://reactjs.org/)
- [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started)
### Communicating with the server
Aside from websocket listeners (see below), all HTTP calls to the Pattern server are defined in `src/services/api.js`.

### Instantiating Link
You'll notice that we don't create a Link instance until the user clicks the Link button. This is because we need information about which user or item to set up Link for before we can create the instance, both for the purposes of setting the necessary callbacks and for letting Plaid know whether we're adding a new item or updating an existing one. Note also that we maintain each individual Link instance indefinitely after creation, so we don't need to repeatedly recreate the same instances for the same users and items. This has a minimal memory footprint relative to the initial load of the Link SDK.

### Webhooks and Websockets
The Pattern server is configured to send a message over a websocket whenever it receives a webhook from Plaid. On the client side have websocket listeners defined in `src/components/Sockets.jsx` that wait for these messages and update data in real time accordingly.

[cra]: https://github.com/facebook/create-react-app
[plaid-link]: https://plaid.com/docs/#integrating-with-link
[react]: https://reactjs.org/
2 changes: 1 addition & 1 deletion client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "client",
"version": "1.0.2",
"version": "1.0.3",
"private": true,
"dependencies": {
"axios": "^0.18.0",
Expand Down
19 changes: 10 additions & 9 deletions client/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -235,23 +235,24 @@ Item overview
}

.item-card__column-1 {
width: 17%;
float: left;
margin-top: 8px;
}

.bank-name {
width: 20%;
display: flex;
float: left;
align-items: center;
}

.item-card__column {
width: 43%;
.item-card__column-2 {
width: 45%;
float: left;
margin-top: 8px;
}

.item-card__column-2 {
.item-card__column-3 {
width: 15%;
float: left;
}

.item-card__column-4 {
width: 20%;
float: left;
}
Expand Down
12 changes: 6 additions & 6 deletions client/src/components/ItemCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const ItemCard = ({ item, userId }) => {
formatLogoSrc,
} = useInstitutions();

const { id, plaid_institution_id, plaid_item_id, status } = item;
const { id, plaid_institution_id, status } = item;
const isSandbox = PLAID_ENV === 'sandbox';
const isGoodState = status === 'good';

Expand Down Expand Up @@ -60,26 +60,26 @@ const ItemCard = ({ item, userId }) => {
className="item-card__clickable"
onClick={() => setShowAccounts(current => !current)}
>
<div className="item-card__column-1 bank-name">
<div className="item-card__column-1">
<img
className="item-card__img"
src={formatLogoSrc(institution.logo)}
alt={institution && institution.name}
/>
<p>{institution && institution.name}</p>
</div>
<div className="item-card__column-1">
<div className="item-card__column-2">
{isGoodState ? (
<div className="item-card__status">Updated</div>
) : (
<div className="item-card__status bad">Login Required</div>
)}
</div>
<div className="item-card__column">
<div className="item-card__column-3">
<h3 className="heading">ITEM_ID</h3>
<p className="value">{plaid_item_id}</p>
<p className="value">{id}</p>
</div>
<div className="item-card__column-2">
<div className="item-card__column-4">
<h3 className="heading">LAST_UPDATED</h3>
<p className="value">{diffBetweenCurrentTime(item.updated_at)}</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/ItemList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const ItemList = ({ match }) => {
>
items
</a>
. Click on an item, to view the accounts within.
. Click on an item to view its associated accounts.
</p>
)}
</div>
Expand Down
5 changes: 2 additions & 3 deletions client/src/components/Landing.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ export default function Landing({ users }) {
<div className="landing__column">
<h3 className="heading">STEP 1</h3>
<p className="value landing__value">
Select or add a user from the list below, and click the Link an item
button below to connect{' '}
Add a user, and click the "Link an Item" button below to connect{' '}
<a
href="https://plaid.com/docs/quickstart/#item-overview"
target="_blank"
rel="noopener noreferrer"
>
items
Items
</a>{' '}
from the user.
</p>
Expand Down
37 changes: 22 additions & 15 deletions client/src/components/Sockets.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,37 @@ export default function Sockets() {
useEffect(() => {
socket.current = io(`localhost:${REACT_APP_SERVER_PORT}`);

socket.current.on('DEFAULT_UPDATE', ({ message, itemId } = {}) =>
console.log(message, itemId)
);
socket.current.on('DEFAULT_UPDATE', ({ itemId } = {}) => {
const msg = `New Webhook Event: Item ${itemId}: New Transactions Received`;
console.log(msg);
toast(msg);
});

socket.current.on('TRANSACTIONS_REMOVED', ({ message, itemId } = {}) =>
console.log(message, itemId)
);
socket.current.on('TRANSACTIONS_REMOVED', ({ itemId } = {}) => {
const msg = `New Webhook Event: Item ${itemId}: Transactions Removed`;
console.log(msg);
toast(msg);
});

socket.current.on('INITIAL_UPDATE', ({ message, itemId } = {}) => {
console.log(message);
toast('New Webhook Event:\nInitial Transactions Received');
socket.current.on('INITIAL_UPDATE', ({ itemId } = {}) => {
const msg = `New Webhook Event: Item ${itemId}: Initial Transactions Received`;
console.log(msg);
toast(msg);
getAccountsByItem(itemId);
getTransactionsByItem(itemId);
});

socket.current.on('HISTORICAL_UPDATE', ({ message, itemId } = {}) => {
console.log(message);
toast('New Webhook Event:\nHistorical Transactions Received');
socket.current.on('HISTORICAL_UPDATE', ({ itemId } = {}) => {
const msg = `New Webhook Event: Item ${itemId}: Historical Transactions Received`;
console.log(msg);
toast(msg);
getTransactionsByItem(itemId, true);
});

socket.current.on('ERROR', ({ message, itemId, errorCode } = {}) => {
console.log(message);
toast.error(`New Webhook Event:\nItem Error ${errorCode}`);
socket.current.on('ERROR', ({ itemId, errorCode } = {}) => {
const msg = `New Webhook Event: Item ${itemId}: Item Error ${errorCode}`;
console.error(msg);
toast.error(msg);
getItemById(itemId, true);
});

Expand Down
41 changes: 15 additions & 26 deletions client/src/services/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const getItemsByUser = userId => api.get(`/users/${userId}/items`);
export const deleteItemById = id => api.delete(`/items/${id}`);
export const setItemState = (itemId, status) =>
api.put(`items/${itemId}`, { status });
// This endoint is only availble in the sandbox enviornment
// This endpoint is only availble in the sandbox enviornment
export const setItemToBadState = itemId =>
api.post('/items/sandbox/item/reset_login', { itemId });

Expand All @@ -37,8 +37,6 @@ export const getAccountsByItem = itemId => api.get(`/items/${itemId}/accounts`);
export const getAccountsByUser = userId => api.get(`/users/${userId}/accounts`);

// transactions
// export const getTransactionsByAccount = accountId =>
// api.get(`/accounts/${accountId}/transactions`);
export const getTransactionsByAccount = accountId =>
api.get(`/accounts/${accountId}/transactions`);
export const getTransactionsByItem = itemId =>
Expand All @@ -50,10 +48,11 @@ export const getTransactionsByUser = userId =>
export const getInstitutionById = instId => api.get(`/institutions/${instId}`);

// misc
export const postLinkEvent = event => api.post(`/link-event`, event);
export const getWebhookUrl = async () => {
try {
const res = await fetch('/services/ngrok');
const { url: urlBase } = await res.json();
const { data } = await api.get('/services/ngrok');
const { url: urlBase } = data;

return {
data: urlBase ? `${urlBase}/services/webhook` : '',
Expand All @@ -70,30 +69,20 @@ export const exchangeToken = async (
accounts,
userId
) => {
const res = await fetch('/items', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
try {
const { data } = await api.post('/items', {
publicToken,
institutionId: institution.institution_id,
userId,
accounts,
}),
});

const resJson = await res.json();

if (res.status === 409) {
const errorMsg = `${institution.name} already linked.`;
console.error(errorMsg);
toast.error(errorMsg);
} else if (resJson.error) {
console.error(resJson.error);
toast.error(`Error linking ${institution.name}`);
});
return data;
} catch (err) {
const { response } = err;
if (response && response.status === 409) {
toast.error(`${institution.name} already linked.`);
} else {
toast.error(`Error linking ${institution.name}`);
}
}

return resJson;
};
Loading

0 comments on commit 0cd1311

Please sign in to comment.