Skip to content

Commit 0cd1311

Browse files
authored
Update to v1.0.3 (#4)
* 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
1 parent bef1c33 commit 0cd1311

35 files changed

+623
-277
lines changed

README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Plaid Pattern (Beta)
1+
# Plaid Pattern
22

33
![Plaid Pattern client][client-img]
44

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

99
## Requirements
1010

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

1414
## Getting Started
@@ -38,8 +38,16 @@ This is a reference application demonstrating an end-to-end [Plaid][plaid] integ
3838
All available commands can be seen by calling `make help`.
3939
4040
## Architecture
41+
As a modern full-stack application, Pattern consists of multiple services handling different segments of the stack:
4142
42-
For more information about the individual services, see the readmes for the [client][client-readme], [database][database-readme], and [server][server-readme].
43+
- [`database`][database-readme] runs a [PostgreSQL][postgres] database
44+
- [`server`][server-readme] runs an application back-end server using [NodeJS] and [Express]
45+
- [`client`][client-readme] runs a [React]-based single-page web frontend
46+
- [`ngrok`][ngrok-readme] exposes a [ngrok] tunnel from your local machine to the Internet to receive webhooks
47+
48+
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.
49+
50+
For more information about the individual services, see their readmes, linked in the list above.
4351
4452
## Troubleshooting
4553
@@ -59,13 +67,20 @@ See [`docs/troubleshooting.md`][troubleshooting].
5967
[client-img]: docs/pattern_screenshot.png
6068
[client-readme]: client/README.md
6169
[database-readme]: database/README.md
62-
[docker]: https://www.docker.com/products/docker-desktop
70+
[docker]: https://docs.docker.com/
71+
[docker-compose]: https://docs.docker.com/compose/
72+
[express]: https://expressjs.com/
73+
[ngrok]: https://ngrok.com/
74+
[ngrok-readme]: ngrok/README.md
75+
[nodejs]: https://nodejs.org/en/
6376
[plaid]: https://plaid.com
6477
[plaid-docs]: https://plaid.com/docs/
6578
[plaid-help]: https://support.plaid.com/hc/en-us
6679
[plaid-keys]: https://dashboard.plaid.com/account/keys
6780
[plaid-quickstart]: https://plaid.com/docs/quickstart/
6881
[plaid-signup]: https://dashboard.plaid.com/signup
6982
[plaid-support-ticket]: https://dashboard.plaid.com/support/new
83+
[postgres]: https://www.postgresql.org/
84+
[react]: http://reactjs.org/
7085
[server-readme]: server/README.md
7186
[troubleshooting]: docs/troubleshooting.md

client/README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
# Plaid Pattern - Client
22

3-
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).
3+
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).
44

5-
## Learn More
5+
## Key concepts
66

7-
- [React documentation](https://reactjs.org/)
8-
- [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started)
7+
### Communicating with the server
8+
Aside from websocket listeners (see below), all HTTP calls to the Pattern server are defined in `src/services/api.js`.
9+
10+
### Instantiating Link
11+
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.
12+
13+
### Webhooks and Websockets
14+
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.
15+
16+
[cra]: https://github.com/facebook/create-react-app
17+
[plaid-link]: https://plaid.com/docs/#integrating-with-link
18+
[react]: https://reactjs.org/

client/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "client",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"private": true,
55
"dependencies": {
66
"axios": "^0.18.0",

client/src/App.css

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,23 +235,24 @@ Item overview
235235
}
236236

237237
.item-card__column-1 {
238-
width: 17%;
239-
float: left;
240-
margin-top: 8px;
241-
}
242-
243-
.bank-name {
244238
width: 20%;
245239
display: flex;
240+
float: left;
246241
align-items: center;
247242
}
248243

249-
.item-card__column {
250-
width: 43%;
244+
.item-card__column-2 {
245+
width: 45%;
251246
float: left;
247+
margin-top: 8px;
252248
}
253249

254-
.item-card__column-2 {
250+
.item-card__column-3 {
251+
width: 15%;
252+
float: left;
253+
}
254+
255+
.item-card__column-4 {
255256
width: 20%;
256257
float: left;
257258
}

client/src/components/ItemCard.jsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const ItemCard = ({ item, userId }) => {
3030
formatLogoSrc,
3131
} = useInstitutions();
3232

33-
const { id, plaid_institution_id, plaid_item_id, status } = item;
33+
const { id, plaid_institution_id, status } = item;
3434
const isSandbox = PLAID_ENV === 'sandbox';
3535
const isGoodState = status === 'good';
3636

@@ -60,26 +60,26 @@ const ItemCard = ({ item, userId }) => {
6060
className="item-card__clickable"
6161
onClick={() => setShowAccounts(current => !current)}
6262
>
63-
<div className="item-card__column-1 bank-name">
63+
<div className="item-card__column-1">
6464
<img
6565
className="item-card__img"
6666
src={formatLogoSrc(institution.logo)}
6767
alt={institution && institution.name}
6868
/>
6969
<p>{institution && institution.name}</p>
7070
</div>
71-
<div className="item-card__column-1">
71+
<div className="item-card__column-2">
7272
{isGoodState ? (
7373
<div className="item-card__status">Updated</div>
7474
) : (
7575
<div className="item-card__status bad">Login Required</div>
7676
)}
7777
</div>
78-
<div className="item-card__column">
78+
<div className="item-card__column-3">
7979
<h3 className="heading">ITEM_ID</h3>
80-
<p className="value">{plaid_item_id}</p>
80+
<p className="value">{id}</p>
8181
</div>
82-
<div className="item-card__column-2">
82+
<div className="item-card__column-4">
8383
<h3 className="heading">LAST_UPDATED</h3>
8484
<p className="value">{diffBetweenCurrentTime(item.updated_at)}</p>
8585
</div>

client/src/components/ItemList.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ const ItemList = ({ match }) => {
6969
>
7070
items
7171
</a>
72-
. Click on an item, to view the accounts within.
72+
. Click on an item to view its associated accounts.
7373
</p>
7474
)}
7575
</div>

client/src/components/Landing.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@ export default function Landing({ users }) {
2020
<div className="landing__column">
2121
<h3 className="heading">STEP 1</h3>
2222
<p className="value landing__value">
23-
Select or add a user from the list below, and click the Link an item
24-
button below to connect{' '}
23+
Add a user, and click the "Link an Item" button below to connect{' '}
2524
<a
2625
href="https://plaid.com/docs/quickstart/#item-overview"
2726
target="_blank"
2827
rel="noopener noreferrer"
2928
>
30-
items
29+
Items
3130
</a>{' '}
3231
from the user.
3332
</p>

client/src/components/Sockets.jsx

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,37 @@ export default function Sockets() {
1515
useEffect(() => {
1616
socket.current = io(`localhost:${REACT_APP_SERVER_PORT}`);
1717

18-
socket.current.on('DEFAULT_UPDATE', ({ message, itemId } = {}) =>
19-
console.log(message, itemId)
20-
);
18+
socket.current.on('DEFAULT_UPDATE', ({ itemId } = {}) => {
19+
const msg = `New Webhook Event: Item ${itemId}: New Transactions Received`;
20+
console.log(msg);
21+
toast(msg);
22+
});
2123

22-
socket.current.on('TRANSACTIONS_REMOVED', ({ message, itemId } = {}) =>
23-
console.log(message, itemId)
24-
);
24+
socket.current.on('TRANSACTIONS_REMOVED', ({ itemId } = {}) => {
25+
const msg = `New Webhook Event: Item ${itemId}: Transactions Removed`;
26+
console.log(msg);
27+
toast(msg);
28+
});
2529

26-
socket.current.on('INITIAL_UPDATE', ({ message, itemId } = {}) => {
27-
console.log(message);
28-
toast('New Webhook Event:\nInitial Transactions Received');
30+
socket.current.on('INITIAL_UPDATE', ({ itemId } = {}) => {
31+
const msg = `New Webhook Event: Item ${itemId}: Initial Transactions Received`;
32+
console.log(msg);
33+
toast(msg);
2934
getAccountsByItem(itemId);
3035
getTransactionsByItem(itemId);
3136
});
3237

33-
socket.current.on('HISTORICAL_UPDATE', ({ message, itemId } = {}) => {
34-
console.log(message);
35-
toast('New Webhook Event:\nHistorical Transactions Received');
38+
socket.current.on('HISTORICAL_UPDATE', ({ itemId } = {}) => {
39+
const msg = `New Webhook Event: Item ${itemId}: Historical Transactions Received`;
40+
console.log(msg);
41+
toast(msg);
3642
getTransactionsByItem(itemId, true);
3743
});
3844

39-
socket.current.on('ERROR', ({ message, itemId, errorCode } = {}) => {
40-
console.log(message);
41-
toast.error(`New Webhook Event:\nItem Error ${errorCode}`);
45+
socket.current.on('ERROR', ({ itemId, errorCode } = {}) => {
46+
const msg = `New Webhook Event: Item ${itemId}: Item Error ${errorCode}`;
47+
console.error(msg);
48+
toast.error(msg);
4249
getItemById(itemId, true);
4350
});
4451

client/src/services/api.js

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const getItemsByUser = userId => api.get(`/users/${userId}/items`);
2626
export const deleteItemById = id => api.delete(`/items/${id}`);
2727
export const setItemState = (itemId, status) =>
2828
api.put(`items/${itemId}`, { status });
29-
// This endoint is only availble in the sandbox enviornment
29+
// This endpoint is only availble in the sandbox enviornment
3030
export const setItemToBadState = itemId =>
3131
api.post('/items/sandbox/item/reset_login', { itemId });
3232

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

3939
// transactions
40-
// export const getTransactionsByAccount = accountId =>
41-
// api.get(`/accounts/${accountId}/transactions`);
4240
export const getTransactionsByAccount = accountId =>
4341
api.get(`/accounts/${accountId}/transactions`);
4442
export const getTransactionsByItem = itemId =>
@@ -50,10 +48,11 @@ export const getTransactionsByUser = userId =>
5048
export const getInstitutionById = instId => api.get(`/institutions/${instId}`);
5149

5250
// misc
51+
export const postLinkEvent = event => api.post(`/link-event`, event);
5352
export const getWebhookUrl = async () => {
5453
try {
55-
const res = await fetch('/services/ngrok');
56-
const { url: urlBase } = await res.json();
54+
const { data } = await api.get('/services/ngrok');
55+
const { url: urlBase } = data;
5756

5857
return {
5958
data: urlBase ? `${urlBase}/services/webhook` : '',
@@ -70,30 +69,20 @@ export const exchangeToken = async (
7069
accounts,
7170
userId
7271
) => {
73-
const res = await fetch('/items', {
74-
method: 'POST',
75-
headers: {
76-
Accept: 'application/json',
77-
'Content-Type': 'application/json',
78-
},
79-
body: JSON.stringify({
72+
try {
73+
const { data } = await api.post('/items', {
8074
publicToken,
8175
institutionId: institution.institution_id,
8276
userId,
8377
accounts,
84-
}),
85-
});
86-
87-
const resJson = await res.json();
88-
89-
if (res.status === 409) {
90-
const errorMsg = `${institution.name} already linked.`;
91-
console.error(errorMsg);
92-
toast.error(errorMsg);
93-
} else if (resJson.error) {
94-
console.error(resJson.error);
95-
toast.error(`Error linking ${institution.name}`);
78+
});
79+
return data;
80+
} catch (err) {
81+
const { response } = err;
82+
if (response && response.status === 409) {
83+
toast.error(`${institution.name} already linked.`);
84+
} else {
85+
toast.error(`Error linking ${institution.name}`);
86+
}
9687
}
97-
98-
return resJson;
9988
};

0 commit comments

Comments
 (0)