Skip to content

Commit

Permalink
feat: adds createPlaidLink hook and Plaid Link components
Browse files Browse the repository at this point in the history
  • Loading branch information
thedanchez committed Nov 22, 2024
1 parent 9b60836 commit 5f14526
Show file tree
Hide file tree
Showing 14 changed files with 760 additions and 51 deletions.
128 changes: 89 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,108 @@
# Template: SolidJS Library
<p>
<img width="100%" src="https://assets.solidjs.com/banner?type=Ecosystem&background=tiles&project=solid-plaid-link" alt="solid-plaid-link">
</p>

Template for [SolidJS](https://www.solidjs.com/) library package. Bundling of the library is managed by [tsup](https://tsup.egoist.dev/).
[![NPM Version](https://img.shields.io/npm/v/solid-plaid-link.svg?style=for-the-badge)](https://www.npmjs.com/package/solid-plaid-link) [![Build Status](https://img.shields.io/github/actions/workflow/status/thedanchez/solid-plaid-link/ci.yaml?branch=main&logo=github&style=for-the-badge)](https://github.com/thedanchez/solid-plaid-link/actions/workflows/ci.yaml) [![bun](https://img.shields.io/badge/maintained%20with-bun-cc00ff.svg?style=for-the-badge&logo=bun)](https://bun.sh/)

Other things configured include:
# Solid Plaid Link

- Bun (for dependency management and running scripts)
- TypeScript
- ESLint / Prettier
- Solid Testing Library + Vitest (for testing)
- Playground app using library
- GitHub Actions (for all CI/CD)
Library for integrating with [Plaid Link](https://plaid.com/docs/link/) in your SolidJS applications.

## Getting Started
_Note: This is an unofficial Solid fork based on the official [react-plaid-link](https://github.com/plaid/react-plaid-link) library_

Some pre-requisites before install dependencies:
### Installation

- Install Node Version Manager (NVM)
```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
```
- Install Bun
```bash
curl -fsSL https://bun.sh/install | bash
```
```bash
npm install solid-js @danchez/solid-plaid-link
pnpm add solid-js @danchez/solid-plaid-link
yarn add solid-js @danchez/solid-plaid-link
bun add solid-js @danchez/solid-plaid-link
```

### Installing Dependencies
## Summary

```bash
nvm use
bun install
This library exposes three things:

```tsx
import { createPlaidLink, PlaidLink, PlaidEmbeddedLink } from "@danchez/solid-plaid-link";
```

### Local Development Build
### createPlaidLink

```bash
bun start
Utility hook that dynamically loads the Plaid script and manages the Plaid Link creation for you. Use this hook when you want full control over how and when to display the Plaid Link UI. Regardless of how this hook is used, it takes care of destroying the Plaid Link UI when unmounting on your behalf.

- _Note: A new Plaid Link instance is created any time the configuration props change. This will only happen if you made the configuration props reactive._

In order to initialize Plaid Link via this hook, a [linkToken](https://plaid.com/docs/api/link/#linktokencreate) is required from Plaid. Once Link has been initialized, it returns a temporary `publicToken`. This `publicToken` must then be exchanged for a permanent `accessToken` which is used to make product requests.

- _Note: the `publicToken` to `accessToken` exchange should be handled by your API server that fulfills requests for your SPA._

```tsx
import { createEffect, Match, onMount, Switch } from "solid-js";
import { createPlaidLink } from "@danchez/solid-plaid-link";

const ExampleOne = () => {
const { ready, error, plaidLink } = createPlaidLink(() => ({
token: "linkToken",
onSuccess: (publicToken, metaData) => {},
}));

return (
<Switch>
<Match when={error()}>{error().message}</Match>
<Match when={ready()}>
{ /* use <PlaidLink /> if you just need a button :) */ }
<button onClick={() => { plaidLink().open(); }}>
Open Plaid Link
</button>
</Match>
</Switch>
);
};

const ExampleTwo = () => {
const { ready, plaidLink } = createPlaidLink(() => ({
token: "linkToken",
onSuccess: (publicToken, metaData) => {},
}));

createEffect(() => {
if (!ready()) return;
plaidLink().open();
});

return (...);
};
```

### Linting & Formatting
### PlaidLink

```bash
bun run lint # checks source for lint violations
bun run format # checks source for format violations
A button which opens the Plaid Link UI on click. If there are any issues downloading Plaid or creating the Plaid Link instance, the button will be disabled.

You are free to customize the button with your own styles. Additionally, you can enrich the `disabled` or `onClick` with your own logic alongside the default behaviors if you so choose.

_Note: this component uses `createPlaidLink` under the hood_

bun run lint:fix # fixes lint violations
bun run format:fix # fixes format violations
```tsx
import { PlaidLink } from "@danchez/solid-plaid-link";

const MyComponent = () => (
<PlaidLink token="linkToken" onSuccess={(publicToken, metaData) => { ... }} />
);
```

### Contributing
### PlaidEmbeddedLink

Solid component that renders the embedded version of the Plaid Link UI using a `div` container.

```tsx
import { PlaidEmbeddedLink } from "@danchez/solid-plaid-link";

const MyComponent = () => (
<PlaidEmbeddedLink token="linkToken" onSuccess={(publicToken, metaData) => { ... }} />
);
```

The only requirements when contributing are:
## Feedback

- You keep a clean git history in your branch
- rebasing `main` instead of making merge commits.
- Using proper commit message formats that adhere to [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
- Additionally, squashing (via rebase) commits that are not [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
- CI checks pass before merging into `main`
Feel free to post any issues or suggestions to help improve this library.
Binary file modified bun.lockb
Binary file not shown.
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "solid-plaid-link",
"version": "0.1.0",
"description": "Solid bindings for Plaid Link",
"name": "@danchez/solid-plaid-link",
"version": "1.0.0",
"description": "Library for integrating Plaid Link into SolidJS applications.",
"type": "module",
"author": "Daniel Sanchez <[email protected]>",
"license": "MIT",
Expand Down Expand Up @@ -35,6 +35,7 @@
"eslint-plugin-solid": "^0.14.3",
"jsdom": "^25.0.1",
"prettier": "^3.3.3",
"solid-js": "1.9.3",
"tsup": "^8.3.0",
"tsup-preset-solid": "^2.2.0",
"typescript": "^5.6.2",
Expand All @@ -44,5 +45,8 @@
},
"peerDependencies": {
"solid-js": ">=1.8.0"
},
"dependencies": {
"solid-create-script": "^1.0.0"
}
}
35 changes: 30 additions & 5 deletions playground/App.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
import { createSignal } from "solid-js";
import { createScript } from "solid-create-script";
import { createEffect, createSignal, Show } from "solid-js";

import { Second } from "./Second";
import { Third } from "./Third";

export const App = () => {
const [count, setCount] = createSignal(0);
const [showSecond, setShowSecond] = createSignal(false);
const [showThird, setShowThird] = createSignal(false);

const script = createScript("https://cdn.plaid.com/link/v2/stable/link-initialize.js", { defer: true });

createEffect(() => {
console.log("Script Error State: ", script.error.message);
});

return (
<div>
<div>Playground App</div>
<div>Count: {count()}</div>
<div>Script Loading State: {script.loading.toString()}</div>
<div>Script Error: {script.error?.message}</div>
<button
onClick={() => {
setShowSecond((prev) => !prev);
}}
>
Toggle Second
</button>
<button
onClick={() => {
setCount((prev) => prev + 1);
setShowThird((prev) => !prev);
}}
>
Increment Count
Toggle Third
</button>
<Show when={showSecond()}>
<Second />
</Show>
<Show when={showThird()}>
<Third />
</Show>
</div>
);
};
12 changes: 12 additions & 0 deletions playground/Second.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createScript } from "solid-create-script";

export const Second = () => {
const script = createScript("https://cdn.plaid.com/link/v2/stable/link-initialize.js", { defer: true });

return (
<div>
<div>Second Component</div>
<div>Script Loading State: {script.loading.toString()}</div>
</div>
);
};
12 changes: 12 additions & 0 deletions playground/Third.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createScript } from "solid-create-script";

export const Third = () => {
const script = createScript("https://cdn.plaid.com/link/v2/stable/link-initialize.js", { defer: true });

return (
<div>
<div>Third Component</div>
<div>Script Loading State: {script.loading.toString()}</div>
</div>
);
};
57 changes: 57 additions & 0 deletions src/components/PlaidEmbeddedLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { createScript } from "solid-create-script";
import { createEffect, type JSX, onCleanup, splitProps } from "solid-js";

import { PLAID_LINK_STABLE_URL, PLAID_PROPS } from "../constants";
import type { PlaidLinkOptions } from "../types";

export type PlaidEmbeddedLinkProps = PlaidLinkOptions & JSX.HTMLAttributes<HTMLDivElement>;

/**
* ## Summary
*
* Solid component that renders the embedded version of the Plaid Link UI using a `div` container.
*
* ## Usage
*
* ```tsx
* import { PlaidEmbeddedLink } from "@danchez/solid-plaid-link";
*
* const MyComponent = () => (
* <PlaidEmbeddedLink token="linkToken" onSuccess={(publicToken, metaData) => { ... }} />
* );
*/
const PlaidEmbeddedLink = (props: PlaidEmbeddedLinkProps) => {
let embeddedLinkTarget!: HTMLDivElement;
const [plaidProps, divProps] = splitProps(props, PLAID_PROPS);

const script = createScript(PLAID_LINK_STABLE_URL, {
defer: true,
});

createEffect(() => {
if (script.loading) return;

if (script.error || typeof window === "undefined" || !window.Plaid) {
console.error("Error loading Plaid", script.error?.message);
return;
}

if (!plaidProps.token) {
console.error("A token is required to initialize embedded Plaid Link");
return;
}

// The embedded Link interface doesn't use the `createPlaidLink` hook to manage
// its Plaid Link instance because the embedded Link integration in link-initialize
// maintains its own handler internally.
const { destroy } = window.Plaid.createEmbedded(plaidProps, embeddedLinkTarget);

onCleanup(() => {
destroy();
});
});

return <div {...divProps} ref={embeddedLinkTarget} />;
};

export default PlaidEmbeddedLink;
49 changes: 49 additions & 0 deletions src/components/PlaidLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { type JSX, type ParentProps, splitProps } from "solid-js";

import { PLAID_PROPS } from "../constants";
import createPlaidLink from "../hooks/createPlaidLink";
import type { PlaidLinkOptions } from "../types";

type PlaidLinkProps = PlaidLinkOptions & JSX.ButtonHTMLAttributes<HTMLButtonElement>;

/**
* ## Summary
*
* A button which opens the Plaid Link UI on click. If there are any issues downloading Plaid or creating the Plaid Link
* instance, the button will be disabled.
*
* You are free to customize the button with your own styles. Additionally, you can enrich
* the `disabled` or `onClick` with your own logic alongside the default behaviors if you so choose.
*
* _Note: this component uses `createPlaidLink` under the hood_
*
* ## Usage
*
* ```tsx
* import { PlaidLink } from "@danchez/solid-plaid-link";
*
* const MyComponent = () => (
* <PlaidLink token="linkToken" onSuccess={(publicToken, metaData) => { ... }} />
* );
*/
const PlaidLink = (props: ParentProps<PlaidLinkProps>) => {
const [plaidProps, btnProps] = splitProps(props, PLAID_PROPS);
const [localProps, otherBtnProps] = splitProps(btnProps, ["disabled", "onClick"]);
const { error, plaidLink } = createPlaidLink(() => plaidProps);

return (
<button
{...otherBtnProps}
disabled={Boolean(error()) || Boolean(localProps.disabled)}
onClick={(e) => {
if (typeof localProps.onClick === "function") {
localProps.onClick?.(e);
}

plaidLink().open();
}}
/>
);
};

export default PlaidLink;
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const PLAID_LINK_STABLE_URL = "https://cdn.plaid.com/link/v2/stable/link-initialize.js";

export const PLAID_PROPS = ["onEvent", "onExit", "onLoad", "onSuccess", "receivedRedirectUri", "token"] as const;
Loading

0 comments on commit 5f14526

Please sign in to comment.