├── .node-version ├── .eslintignore ├── .prettierignore ├── .gitignore ├── .babelrc ├── .prettierrc.yml ├── src ├── types.ts ├── index.ts ├── Components.test.ts ├── utils │ ├── useAttachAttribute.ts │ ├── useAttachEvent.ts │ └── useUpdateWithSetter.ts ├── ConnectComponents.tsx ├── useCreateComponent.tsx └── Components.tsx ├── jest.config.js ├── .github ├── workflows │ └── test.yml └── dependabot.yml ├── tsconfig.json ├── CONTRIBUTING.md ├── examples └── components.jsx ├── LICENSE ├── rollup.config.js ├── .eslintrc.yml ├── README.md ├── package.json └── scripts └── publish /.node-version: -------------------------------------------------------------------------------- 1 | 18.20.2 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/**/*.js 2 | dist/**/*.js 3 | examples 4 | .yalc 5 | rollup.config.js -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | es 4 | lib 5 | package.json 6 | examples 7 | .yalc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | es 5 | lib 6 | *.log 7 | .yalc 8 | yalc.lock -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react", "@babel/preset-env", "@babel/preset-flow"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | trailingComma: es5 3 | bracketSpacing: false 4 | arrowParens: always 5 | proseWrap: always 6 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type FetchEphemeralKeyFunction = (fetchParams: { 2 | issuingCard: string; 3 | nonce: string; 4 | }) => Promise<{ 5 | issuingCard: string; 6 | nonce: string; 7 | ephemeralKeySecret: string; 8 | }>; 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Components'; 2 | export * from './ConnectComponents'; 3 | export * from './useCreateComponent'; 4 | export * from './utils/useAttachAttribute'; 5 | export * from './utils/useAttachEvent'; 6 | export * from './utils/useUpdateWithSetter'; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | testMatch: [ 4 | '**/__tests__/**/*.+(ts|tsx|js)', 5 | '**/?(*.)+(spec|test).+(ts|tsx|js)', 6 | ], 7 | transform: { 8 | '^.+\\.(ts|tsx)$': ['ts-jest', {diagnostics: {ignoreCodes: [151001]}}], 9 | }, 10 | testEnvironment: 'jsdom', 11 | }; 12 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: [master, preview] 5 | pull_request: 6 | branches: [master, preview] 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 18.20.2 15 | - run: yarn install --frozen-lockfile 16 | - run: yarn run validate-change 17 | -------------------------------------------------------------------------------- /src/Components.test.ts: -------------------------------------------------------------------------------- 1 | import * as Components from './Components'; 2 | 3 | describe('Components', () => { 4 | it("should verify that every component name starts with 'Connect'", () => { 5 | const doesEveryComponentNameStartsWithConnect = Object.keys( 6 | Components 7 | ).every((componentName) => { 8 | return componentName.startsWith('Connect'); 9 | }); 10 | expect(doesEveryComponentNameStartsWithConnect).toBe(true); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | # Setting to 0 blocks updates for non-security related package dependencies updates 8 | open-pull-requests-limit: 0 9 | target-branch: "master" 10 | 11 | - package-ecosystem: "npm" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | open-pull-requests-limit: 0 16 | target-branch: "preview" -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", // Let Babel deal with transpiling new language features 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "jsx": "react", 7 | "noEmit": true, 8 | "declaration": true, 9 | "allowJs": true, 10 | "removeComments": false, 11 | "strict": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true 15 | }, 16 | "include": ["./src"] 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/useAttachAttribute.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * @deprecated The method should not be used as we are deprecating the use of attributes directly. 5 | * Define a setter for your use case and use useUpdateWithSetter instead. 6 | */ 7 | export const useAttachAttribute = ( 8 | component: HTMLElement | null, 9 | attribute: string, 10 | value: string | boolean | undefined 11 | ): void => { 12 | React.useEffect(() => { 13 | if (!component) return; 14 | 15 | if (value !== undefined) { 16 | component.setAttribute(attribute, value.toString()); 17 | } else { 18 | // RemoveAttribute is a no-op if the attribute is not present 19 | component.removeAttribute(attribute); 20 | } 21 | }, [component, attribute, value]); 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/useAttachEvent.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export enum ConnectElementEventNames { 4 | exit = 'exit', 5 | close = 'close', 6 | instantPayoutCreated = 'instantpayoutcreated', 7 | } 8 | 9 | /** 10 | * @deprecated The method should not be used as we are deprecating the use of events directly. Define a setter for 11 | * your use case and use useUpdateWithSetter instead. 12 | */ 13 | export const useAttachEvent = ( 14 | component: HTMLElement | null, 15 | eventName: ConnectElementEventNames, 16 | listener: () => void 17 | ): void => { 18 | React.useEffect(() => { 19 | if (component) { 20 | component.addEventListener(eventName, listener); 21 | } 22 | return () => { 23 | if (component) { 24 | component.removeEventListener(eventName, listener); 25 | } 26 | }; 27 | }, [component, eventName, listener]); 28 | }; 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions of any kind are welcome! If you've found a bug or have a feature 4 | request, please feel free to 5 | [open an issue](https://github.com/stripe/react-connect-js/issues). 6 | 7 | To make changes yourself, follow these steps: 8 | 9 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository and 10 | [clone](https://help.github.com/articles/cloning-a-repository/) it locally. 11 | 2. Make your changes 12 | 3. Submit a 13 | [pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) 14 | 15 | ## Contributor License Agreement ([CLA](https://en.wikipedia.org/wiki/Contributor_License_Agreement)) 16 | 17 | Once you have submitted a pull request, sign the CLA by clicking on the badge in 18 | the comment from [@CLAassistant](https://github.com/CLAassistant). 19 | 20 | image 21 | 22 |
23 | Thanks for contributing to Stripe! :sparkles: 24 | -------------------------------------------------------------------------------- /examples/components.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | ConnectPayments, 4 | ConnectPayouts, 5 | ConnectPaymentDetails, 6 | ConnectComponentsProvider, 7 | loadConnectAndInitialize 8 | } from '@stripe/react-connect-js'; 9 | 10 | const fetchClientSecret = async () => { 11 | // Fetch the AccountSession client secret by making an API call to your service 12 | }; 13 | const connectInstance = loadConnectAndInitialize({ 14 | publishableKey: '{{your publishable key}}', 15 | fetchClientSecret: fetchClientSecret, 16 | appearance: { 17 | variables: { 18 | colorPrimary: '#228403', //optional appearance param, 19 | }, 20 | } 21 | }); 22 | 23 | const App = () => { 24 | return ( 25 | 26 | 27 | 28 | { 30 | console.log('closed'); 31 | }} 32 | payment="pi_test123" 33 | /> 34 | 35 | ); 36 | }; 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Stripe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ConnectComponents.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type * as connectJs from '@stripe/connect-js'; 3 | 4 | type ConnectComponentsPayload = { 5 | connectInstance: connectJs.StripeConnectInstance; 6 | }; 7 | 8 | const ConnectComponentsContext = 9 | React.createContext(null); 10 | 11 | ConnectComponentsContext.displayName = 'ConnectComponents'; 12 | 13 | export const ConnectComponentsProvider = ({ 14 | connectInstance, 15 | children, 16 | }: { 17 | connectInstance: connectJs.StripeConnectInstance; 18 | children: any; 19 | }): JSX.Element => { 20 | return ( 21 | 22 | {children} 23 | 24 | ); 25 | }; 26 | 27 | export const useConnectComponents = (): ConnectComponentsPayload => { 28 | const context = React.useContext(ConnectComponentsContext); 29 | if (!context) { 30 | throw new Error( 31 | `Could not find Components context; You need to wrap the part of your app in an provider.` 32 | ); 33 | } 34 | return context; 35 | }; 36 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import resolve from 'rollup-plugin-node-resolve'; 4 | import {terser} from 'rollup-plugin-terser'; 5 | import replace from 'rollup-plugin-replace'; 6 | import ts from 'rollup-plugin-ts'; 7 | import pkg from './package.json'; 8 | import json from '@rollup/plugin-json'; 9 | 10 | const PLUGINS = [ 11 | ts({ 12 | tsconfigOverride: {exclude: ['**/*.test.ts']}, 13 | }), 14 | babel({ 15 | extensions: ['.ts', '.js', '.tsx', '.jsx'], 16 | }), 17 | replace({ 18 | // This string is replaced by the npm package version when bundling 19 | _NPM_PACKAGE_VERSION_: pkg.version, 20 | }), 21 | json(), 22 | ]; 23 | 24 | export default [ 25 | { 26 | input: 'src/index.ts', 27 | output: [ 28 | {file: pkg.main, format: 'cjs'}, 29 | {file: pkg.module, format: 'es'}, 30 | ], 31 | plugins: PLUGINS, 32 | }, 33 | // UMD build with inline PropTypes 34 | { 35 | input: 'src/index.ts', 36 | external: ['react'], 37 | output: [ 38 | { 39 | name: 'ReactConnect', 40 | file: pkg.browser, 41 | format: 'umd', 42 | globals: { 43 | react: 'React', 44 | }, 45 | }, 46 | ], 47 | plugins: PLUGINS, 48 | }, 49 | ]; 50 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | root: true 3 | extends: 4 | - eslint:recommended 5 | - plugin:@typescript-eslint/eslint-recommended 6 | - plugin:@typescript-eslint/recommended 7 | - plugin:react/recommended 8 | parser: "@typescript-eslint/parser" 9 | parserOptions: 10 | # project: './tsconfig.json' 11 | ecmaFeatures: 12 | jsx: true 13 | plugins: 14 | - "@typescript-eslint" 15 | - jest 16 | - react 17 | - react-hooks 18 | settings: 19 | react: 20 | version: detect 21 | env: 22 | jest/globals: true 23 | browser: true 24 | es6: true 25 | rules: 26 | no-console: 0 27 | func-style: 2 28 | consistent-return: 2 29 | prefer-arrow-callback: 30 | - 2 31 | - allowNamedFunctions: false 32 | allowUnboundThis: false 33 | jest/no-disabled-tests: 2 34 | jest/no-focused-tests: 2 35 | react/prop-types: 0 36 | react/forbid-prop-types: 0 37 | react/no-unused-prop-types: 0 38 | react-hooks/rules-of-hooks: 2 39 | react-hooks/exhaustive-deps: 1 40 | "@typescript-eslint/no-explicit-any": 0 41 | "@typescript-eslint/no-empty-interface": 0 42 | "@typescript-eslint/explicit-function-return-type": 0 43 | "@typescript-eslint/camelcase": 0 44 | "@typescript-eslint/no-empty-function": 0 45 | "@typescript-eslint/no-unused-vars": 46 | - 2 47 | - varsIgnorePattern: ^_ 48 | "@typescript-eslint/consistent-type-imports": "error" 49 | -------------------------------------------------------------------------------- /src/utils/useUpdateWithSetter.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type {CollectionOptions} from '../Components'; 3 | import type { 4 | LoadError, 5 | LoaderStart, 6 | NotificationCount, 7 | StepChange, 8 | PaymentsListDefaultFilters, 9 | } from '@stripe/connect-js'; 10 | import type {FetchEphemeralKeyFunction} from '../types'; 11 | 12 | export const useUpdateWithSetter = < 13 | T extends HTMLElement, 14 | V extends 15 | | string 16 | | boolean 17 | | string[] 18 | | PaymentsListDefaultFilters 19 | | (() => void) 20 | | FetchEphemeralKeyFunction 21 | | CollectionOptions 22 | | ((notificationCount: NotificationCount) => void) 23 | | ((loaderStart: LoaderStart) => void) 24 | | ((loaderError: LoadError) => void) 25 | | ((stepChange: StepChange) => void) 26 | | (({id}: {id: string}) => void) 27 | | (({promotionShown}: {promotionShown: boolean}) => void) 28 | | (({payoutId}: {payoutId: string}) => void) 29 | | undefined 30 | >( 31 | component: T | null, 32 | value: V, 33 | onUpdated: (component: T, value: V) => void 34 | ): void => { 35 | React.useEffect(() => { 36 | if (!component) return; 37 | 38 | try { 39 | onUpdated(component, value); 40 | } catch (error) { 41 | console.error('Error when calling setter! ', error); 42 | return; 43 | } 44 | }, [component, value, onUpdated]); 45 | }; 46 | -------------------------------------------------------------------------------- /src/useCreateComponent.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import * as React from 'react'; 3 | import {useConnectComponents} from './ConnectComponents'; 4 | import type { 5 | ConnectElementTagName, 6 | ConnectHTMLElementRecord, 7 | } from '@stripe/connect-js'; 8 | 9 | export const useCreateComponent = ( 10 | tagName: T 11 | ): {wrapper: JSX.Element; component: ConnectHTMLElementRecord[T] | null} => { 12 | const [component, setComponent] = React.useState< 13 | ConnectHTMLElementRecord[T] | null 14 | >(null); 15 | const {connectInstance} = useConnectComponents(); 16 | const wrapperDivRef = React.useRef(null); 17 | 18 | // We set width to 100% to preserve this functionality aspect of embedded components even though 19 | // we are introducing a wrapper div for this element 20 | // https://docs.corp.stripe.com/connect/get-started-connect-embedded-components#width-and-height 21 | const wrapper =
; 22 | 23 | React.useLayoutEffect(() => { 24 | if (wrapperDivRef.current !== null && component === null) { 25 | const newComponent = connectInstance.create(tagName); 26 | 27 | setComponent(newComponent); 28 | if (newComponent !== null) { 29 | try { 30 | newComponent.setAttribute( 31 | 'reactSdkAnalytics', 32 | '_NPM_PACKAGE_VERSION_' 33 | ); 34 | } catch (e) { 35 | console.log( 36 | 'Error setting React Sdk version with error message: ', 37 | e 38 | ); 39 | } 40 | while (wrapperDivRef.current.firstChild) { 41 | wrapperDivRef.current.removeChild(wrapperDivRef.current.firstChild); 42 | } 43 | 44 | wrapperDivRef.current.appendChild(newComponent); 45 | } 46 | } 47 | }, [connectInstance, tagName]); 48 | 49 | return {wrapper, component}; 50 | }; 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @stripe/react-connect-js 2 | 3 | # React Connect.js 4 | 5 | React components for Connect.js and Connect embedded components 6 | 7 | This project is a thin React wrapper around 8 | [Connect embedded components](https://stripe.com/docs/connect/get-started-connect-embedded-components). 9 | It allows you to add embedded components to any React app, and manages the state 10 | and lifecycle of embedded components for you. 11 | 12 | Note: Some Connect embedded components are currently still in preview. These can 13 | be 14 | [viewed on our doc site](https://docs.stripe.com/connect/supported-embedded-components), 15 | where you can also request preview access. 16 | 17 | ### Components 18 | 19 | The list of supported components and their required parameters can be found 20 | [here](https://stripe.com/docs/connect/supported-embedded-components) 21 | 22 | ### Minimal example 23 | 24 | First, install React Connect.js and 25 | [Connect.js](https://github.com/stripe/connect-js). 26 | 27 | ```sh 28 | npm install @stripe/react-connect-js @stripe/connect-js 29 | ``` 30 | 31 | You have to wrap your components with `ConnectComponentsProvider` in order to 32 | provide the ConnectInstance context to create or update components 33 | 34 | #### Example 35 | 36 | See more examples in the /examples folder 37 | 38 | ```jsx 39 | import React from 'react'; 40 | import ReactDOM from 'react-dom'; 41 | import {loadConnectAndInitialize} from '@stripe/connect-js'; 42 | import { 43 | ConnectPayments, 44 | ConnectPayouts, 45 | ConnectPaymentDetails, 46 | ConnectComponentsProvider, 47 | } from '@stripe/react-connect-js'; 48 | 49 | const fetchClientSecret = async () => { 50 | // Fetch the AccountSession client secret by making an API call to your service 51 | }; 52 | const connectInstance = loadConnectAndInitialize({ 53 | publishableKey: '{{pk test123}}', 54 | fetchClientSecret: fetchClientSecret, 55 | appearance: { 56 | variables: { 57 | colorPrimary: '#228403', //optional appearance param, 58 | }, 59 | }, 60 | }); 61 | 62 | const App = () => ( 63 | 64 | 65 | 66 | { 68 | console.log('closed'); 69 | }} 70 | payment="pi_test123" 71 | /> 72 | 73 | ); 74 | 75 | ReactDOM.render(, document.body); 76 | ``` 77 | 78 | The `stripeConnect.initalize` function returns a `ConnectInstance` once you 79 | initialize it with a publishable key and a client secret returned from the 80 | [Account Session API](https://stripe.com/docs/api/account_sessions/create) call. 81 | 82 | We’ve placed a placeholder API key in this example. Replace it with your 83 | [actual publishable API keys](https://dashboard.stripe.com/account/apikeys) to 84 | test this code through your Connect account. 85 | 86 | ### Contributing 87 | 88 | If you would like to contribute to React Connect.js, please make sure to read 89 | our [contributor guidelines](CONTRIBUTING.md). 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@stripe/react-connect-js", 3 | "version": "3.3.31", 4 | "description": "React components for Connect.js and Connect embedded components", 5 | "main": "dist/react-connect.js", 6 | "module": "dist/react-connect.esm.js", 7 | "types": "dist/react-connect.d.ts", 8 | "files": [ 9 | "dist", 10 | "src", 11 | "node_modules", 12 | ".yalc" 13 | ], 14 | "scripts": { 15 | "test": "yarn run lint && yarn run test:unit --passWithNoTests && yarn run typecheck", 16 | "test:unit": "jest", 17 | "lint": "eslint --max-warnings=0 '{src,examples}/**/*.{ts,tsx,js}'", 18 | "lint:prettier": "prettier './**/*.js' './**/*.ts' './**/*.tsx' './**/*.md' --list-different", 19 | "lint-fix": "eslint '{src,types}/**/*.{ts,js}' --fix && yarn prettier", 20 | "typecheck": "tsc", 21 | "build": "yarn run clean && yarn run rollup -c", 22 | "checkimport": "scripts/check-imports", 23 | "clean": "rimraf dist", 24 | "prettier": "prettier './**/*.js' './**/*.ts' './**/*.tsx' './**/*.md' --write", 25 | "prettier-check": "prettier './**/*.{js,ts,md,html,css}' --check", 26 | "doctoc": "doctoc README.md", 27 | "storybook": "start-storybook -p 6006 ", 28 | "local-install-publish": "yalc add @stripe/connect-js@1.0.0 && yalc publish", 29 | "validate-change": "yarn run test", 30 | "validate-changes": "yarn run test" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/stripe/react-connect-js.git" 35 | }, 36 | "keywords": [ 37 | "React", 38 | "Connect", 39 | "embedded UIs" 40 | ], 41 | "author": "Stripe (https://www.stripe.com)", 42 | "license": "MIT", 43 | "bugs": { 44 | "url": "https://github.com/stripe/react-connect-js/issues" 45 | }, 46 | "homepage": "https://github.com/stripe/react-connect-js#readme", 47 | "devDependencies": { 48 | "@babel/core": "^7.7.2", 49 | "@babel/preset-env": "^7.7.1", 50 | "@babel/preset-flow": "7.18.6", 51 | "@babel/preset-react": "7.18.6", 52 | "@rollup/plugin-json": "^6.0.0", 53 | "@rollup/plugin-replace": "^2.3.1", 54 | "@stripe/connect-js": "3.3.31", 55 | "@types/jest": "^24.0.25", 56 | "@types/react": "^16.8.0", 57 | "@types/react-dom": "^16.8.0", 58 | "@typescript-eslint/eslint-plugin": "^7", 59 | "@typescript-eslint/parser": "^7", 60 | "@typescript-eslint/rule-tester": "^7", 61 | "@typescript-eslint/scope-manager": "^7", 62 | "@typescript-eslint/utils": "^7", 63 | "babel-eslint": "^10.0.3", 64 | "babel-jest": "^24.9.0", 65 | "conditional-type-checks": "^1.0.5", 66 | "eslint": "8.56.0", 67 | "eslint-config-prettier": "^8.3.0", 68 | "eslint-plugin-import": "^2.20.1", 69 | "eslint-plugin-jest": "^26.6.0", 70 | "eslint-plugin-prettier": "^4.2.1", 71 | "eslint-plugin-react": "^7.36.1", 72 | "eslint-plugin-react-hooks": "5.2.0-canary-7b402084-20250107", 73 | "jest": "^29.5.0", 74 | "jest-environment-jsdom": "^29.5.0", 75 | "jsdom": "^22.0.0", 76 | "prettier": "^2.8.7", 77 | "react": "^16.14.0", 78 | "rimraf": "^2.6.2", 79 | "rollup": "~2.79", 80 | "rollup-plugin-babel": "^4.4.0", 81 | "rollup-plugin-commonjs": "^10.1.0", 82 | "rollup-plugin-node-resolve": "^5.2.0", 83 | "rollup-plugin-replace": "^2.2.0", 84 | "rollup-plugin-terser": "^7.0.2", 85 | "rollup-plugin-ts": "^3.2.0", 86 | "rollup-plugin-typescript2": "^0.25.3", 87 | "ts-jest": "^29.1.0", 88 | "typescript": "^4.1.2", 89 | "yalc": "^1.0.0-pre.53", 90 | "zx": "^4.2.0" 91 | }, 92 | "peerDependencies": { 93 | "@stripe/connect-js": ">=3.3.29", 94 | "react": ">=16.8.0", 95 | "react-dom": ">=16.8.0" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /scripts/publish: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | IFS=$'\n\t' 5 | 6 | RELEASE_TYPE=${1:-} 7 | 8 | echo_help() { 9 | cat << EOF 10 | USAGE: 11 | ./scripts/publish 12 | 13 | ARGS: 14 | 15 | A Semantic Versioning release type used to bump the version number. Either "patch", "minor", "major", "prepatch", "preminor", "premajor", or "prerelease". 16 | EOF 17 | } 18 | 19 | create_github_release() { 20 | if which hub | grep -q "not found"; then 21 | create_github_release_fallback 22 | return 23 | fi 24 | 25 | # Get the last two releases. For example, `("v1.3.1" "v1.3.2")` 26 | local versions=($(git tag --sort version:refname | grep '^v' | tail -n 2)) 27 | 28 | # If we didn't find exactly two previous version versions, give up 29 | if [ ${#versions[@]} -ne 2 ]; then 30 | create_github_release_fallback 31 | return 32 | fi 33 | 34 | local previous_version="${versions[0]}" 35 | local current_version="${versions[1]}" 36 | local commit_titles=$(git log --pretty=format:"- %s" "$previous_version".."$current_version"^) 37 | local release_notes="$(cat << EOF 38 | $current_version 39 | 40 | 41 | 42 | 43 | $commit_titles 44 | 45 | ### New features 46 | 47 | ### Fixes 48 | 49 | ### Changed 50 | 51 | EOF 52 | )" 53 | 54 | echo "Creating GitHub release" 55 | echo "" 56 | echo -n " " 57 | hub release create -em "$release_notes" "$current_version" 58 | } 59 | 60 | create_github_release_fallback() { 61 | cat << EOF 62 | Remember to create a release on GitHub with a changelog notes: 63 | 64 | https://github.com/stripe/react-connect-js/releases/new 65 | 66 | EOF 67 | } 68 | 69 | # Show help if no arguments passed 70 | if [ $# -eq 0 ]; then 71 | echo "Error! Missing release type argument" 72 | echo "" 73 | echo_help 74 | exit 1 75 | fi 76 | 77 | # Show help message if -h, --help, or help passed 78 | case $1 in 79 | -h | --help | help) 80 | echo_help 81 | exit 0 82 | ;; 83 | esac 84 | 85 | # Validate passed release type 86 | case $RELEASE_TYPE in 87 | patch | minor | major | prepatch | preminor | premajor | prerelease) 88 | ;; 89 | 90 | *) 91 | echo "Error! Invalid release type supplied" 92 | echo "" 93 | echo_help 94 | exit 1 95 | ;; 96 | esac 97 | 98 | # Make sure our working dir is the repo root directory 99 | cd "$(git rev-parse --show-toplevel)" 100 | 101 | echo "Fetching git remotes" 102 | git fetch 103 | 104 | GIT_STATUS=$(git status) 105 | 106 | if ! grep -q 'On branch master' <<< "$GIT_STATUS"; then 107 | echo "Error! Must be on master branch to publish" 108 | exit 1 109 | fi 110 | 111 | if ! grep -q "Your branch is up to date with 'origin/master'." <<< "$GIT_STATUS"; then 112 | echo "Error! Must be up to date with origin/master to publish" 113 | exit 1 114 | fi 115 | 116 | if ! grep -q 'working tree clean' <<< "$GIT_STATUS"; then 117 | echo "Error! Cannot publish with dirty working tree" 118 | exit 1 119 | fi 120 | 121 | echo "Installing dependencies according to lockfile" 122 | yarn -s install --frozen-lockfile 123 | 124 | echo "Running tests" 125 | yarn -s run test 126 | 127 | echo "Bumping package.json $RELEASE_TYPE version and tagging commit" 128 | yarn -s version --$RELEASE_TYPE 129 | 130 | echo "Building" 131 | yarn -s run build 132 | 133 | echo "Publishing release" 134 | yarn --ignore-scripts publish --non-interactive --access=public 135 | 136 | echo "Pushing git commit and tag" 137 | git push --follow-tags 138 | 139 | echo "Publish successful!" 140 | echo "" 141 | 142 | create_github_release -------------------------------------------------------------------------------- /src/Components.tsx: -------------------------------------------------------------------------------- 1 | import type {FetchEphemeralKeyFunction} from './types'; 2 | import {useCreateComponent} from './useCreateComponent'; 3 | import {useUpdateWithSetter} from './utils/useUpdateWithSetter'; 4 | import type { 5 | CollectionOptions, 6 | LoadError, 7 | LoaderStart, 8 | NotificationCount, 9 | StepChange, 10 | PaymentsListDefaultFilters, 11 | } from '@stripe/connect-js'; 12 | 13 | export type {CollectionOptions}; 14 | export type CommonComponentProps = { 15 | onLoaderStart?: ({elementTagName}: LoaderStart) => void; 16 | onLoadError?: ({error, elementTagName}: LoadError) => void; 17 | }; 18 | 19 | export const ConnectPayments = ({ 20 | defaultFilters, 21 | onLoadError, 22 | onLoaderStart, 23 | }: { 24 | defaultFilters?: PaymentsListDefaultFilters; 25 | } & CommonComponentProps): JSX.Element => { 26 | const {wrapper, component: payments} = useCreateComponent('payments'); 27 | 28 | useUpdateWithSetter(payments, onLoaderStart, (comp, val) => { 29 | comp.setOnLoaderStart(val); 30 | }); 31 | useUpdateWithSetter(payments, onLoadError, (comp, val) => { 32 | comp.setOnLoadError(val); 33 | }); 34 | useUpdateWithSetter(payments, defaultFilters, (comp, val) => { 35 | comp.setDefaultFilters(val); 36 | }); 37 | 38 | return wrapper; 39 | }; 40 | 41 | export const ConnectPayouts = ({ 42 | onLoadError, 43 | onLoaderStart, 44 | }: CommonComponentProps): JSX.Element => { 45 | const {wrapper, component: payouts} = useCreateComponent('payouts'); 46 | 47 | useUpdateWithSetter(payouts, onLoaderStart, (comp, val) => { 48 | comp.setOnLoaderStart(val); 49 | }); 50 | useUpdateWithSetter(payouts, onLoadError, (comp, val) => { 51 | comp.setOnLoadError(val); 52 | }); 53 | 54 | return wrapper; 55 | }; 56 | 57 | export const ConnectPaymentDetails = ({ 58 | payment, 59 | onClose, 60 | onLoadError, 61 | onLoaderStart, 62 | }: { 63 | /** 64 | * @param payment the ID of `payment`, `charge`, or `paymentIntent` to be displayed. 65 | */ 66 | payment: string; 67 | onClose: () => void; 68 | } & CommonComponentProps): JSX.Element | null => { 69 | const {wrapper, component: paymentDetails} = 70 | useCreateComponent('payment-details'); 71 | 72 | useUpdateWithSetter(paymentDetails, payment, (comp, val) => 73 | comp.setPayment(val) 74 | ); 75 | useUpdateWithSetter(paymentDetails, onClose, (comp, val) => 76 | comp.setOnClose(val) 77 | ); 78 | useUpdateWithSetter(paymentDetails, onLoaderStart, (comp, val) => { 79 | comp.setOnLoaderStart(val); 80 | }); 81 | useUpdateWithSetter(paymentDetails, onLoadError, (comp, val) => { 82 | comp.setOnLoadError(val); 83 | }); 84 | return wrapper; 85 | }; 86 | 87 | export const ConnectPaymentDisputes = ({ 88 | payment, 89 | onDisputesLoaded, 90 | onLoadError, 91 | onLoaderStart, 92 | }: { 93 | /** 94 | * @param payment the ID of `payment`, `charge`, or `paymentIntent` to be displayed. 95 | */ 96 | payment: string; 97 | onDisputesLoaded?: ({total}: {total: number}) => void; 98 | } & CommonComponentProps): JSX.Element | null => { 99 | const {wrapper, component} = 100 | useCreateComponent('payment-disputes'); 101 | 102 | useUpdateWithSetter(component, payment, (comp, val) => 103 | comp.setPayment(val) 104 | ); 105 | useUpdateWithSetter(component, onDisputesLoaded, (comp, val) => 106 | comp.setOnDisputesLoaded(val) 107 | ); 108 | useUpdateWithSetter(component, onLoaderStart, (comp, val) => { 109 | comp.setOnLoaderStart(val); 110 | }); 111 | useUpdateWithSetter(component, onLoadError, (comp, val) => { 112 | comp.setOnLoadError(val); 113 | }); 114 | return wrapper; 115 | }; 116 | 117 | export const ConnectDisputesList = ({ 118 | onLoadError, 119 | onLoaderStart, 120 | }: CommonComponentProps): JSX.Element | null => { 121 | const {wrapper, component} = 122 | useCreateComponent('disputes-list'); 123 | 124 | useUpdateWithSetter(component, onLoaderStart, (comp, val) => { 125 | comp.setOnLoaderStart(val); 126 | }); 127 | useUpdateWithSetter(component, onLoadError, (comp, val) => { 128 | comp.setOnLoadError(val); 129 | }); 130 | return wrapper; 131 | }; 132 | 133 | export const ConnectAccountOnboarding = ({ 134 | onExit, 135 | recipientTermsOfServiceUrl, 136 | fullTermsOfServiceUrl, 137 | privacyPolicyUrl, 138 | skipTermsOfServiceCollection, 139 | collectionOptions, 140 | onLoadError, 141 | onLoaderStart, 142 | onStepChange, 143 | }: { 144 | onExit: () => void; 145 | onStepChange?: (stepChange: StepChange) => void; 146 | recipientTermsOfServiceUrl?: string; 147 | fullTermsOfServiceUrl?: string; 148 | privacyPolicyUrl?: string; 149 | skipTermsOfServiceCollection?: boolean; 150 | collectionOptions?: CollectionOptions; 151 | } & CommonComponentProps): JSX.Element | null => { 152 | const {wrapper, component: onboarding} = 153 | useCreateComponent('account-onboarding'); 154 | 155 | useUpdateWithSetter(onboarding, recipientTermsOfServiceUrl, (comp, val) => 156 | comp.setRecipientTermsOfServiceUrl(val) 157 | ); 158 | useUpdateWithSetter(onboarding, fullTermsOfServiceUrl, (comp, val) => 159 | comp.setFullTermsOfServiceUrl(val) 160 | ); 161 | useUpdateWithSetter(onboarding, privacyPolicyUrl, (comp, val) => 162 | comp.setPrivacyPolicyUrl(val) 163 | ); 164 | useUpdateWithSetter(onboarding, skipTermsOfServiceCollection, (comp, val) => 165 | comp.setSkipTermsOfServiceCollection(val) 166 | ); 167 | useUpdateWithSetter(onboarding, collectionOptions, (comp, val) => 168 | comp.setCollectionOptions(val) 169 | ); 170 | useUpdateWithSetter(onboarding, onExit, (comp, val) => comp.setOnExit(val)); 171 | useUpdateWithSetter(onboarding, onLoaderStart, (comp, val) => { 172 | comp.setOnLoaderStart(val); 173 | }); 174 | useUpdateWithSetter(onboarding, onLoadError, (comp, val) => { 175 | comp.setOnLoadError(val); 176 | }); 177 | useUpdateWithSetter(onboarding, onStepChange, (comp, val) => 178 | comp.setOnStepChange(val) 179 | ); 180 | 181 | return wrapper; 182 | }; 183 | 184 | export const ConnectAccountManagement = ({ 185 | collectionOptions, 186 | onLoadError, 187 | onLoaderStart, 188 | }: { 189 | collectionOptions?: CollectionOptions; 190 | } & CommonComponentProps): JSX.Element | null => { 191 | const {wrapper, component: accountManagement} = 192 | useCreateComponent('account-management'); 193 | 194 | useUpdateWithSetter(accountManagement, collectionOptions, (comp, val) => 195 | comp.setCollectionOptions(val) 196 | ); 197 | useUpdateWithSetter(accountManagement, onLoaderStart, (comp, val) => { 198 | comp.setOnLoaderStart(val); 199 | }); 200 | useUpdateWithSetter(accountManagement, onLoadError, (comp, val) => { 201 | comp.setOnLoadError(val); 202 | }); 203 | 204 | return wrapper; 205 | }; 206 | 207 | export const ConnectNotificationBanner = ({ 208 | collectionOptions, 209 | onNotificationsChange, 210 | onLoadError, 211 | onLoaderStart, 212 | }: { 213 | collectionOptions?: CollectionOptions; 214 | onNotificationsChange?: ({total, actionRequired}: NotificationCount) => void; 215 | } & CommonComponentProps): JSX.Element | null => { 216 | const {wrapper, component: notificationBanner} = useCreateComponent( 217 | 'notification-banner' 218 | ); 219 | 220 | useUpdateWithSetter(notificationBanner, collectionOptions, (comp, val) => 221 | comp.setCollectionOptions(val) 222 | ); 223 | useUpdateWithSetter(notificationBanner, onNotificationsChange, (comp, val) => 224 | comp.setOnNotificationsChange(val) 225 | ); 226 | useUpdateWithSetter(notificationBanner, onLoaderStart, (comp, val) => { 227 | comp.setOnLoaderStart(val); 228 | }); 229 | useUpdateWithSetter(notificationBanner, onLoadError, (comp, val) => { 230 | comp.setOnLoadError(val); 231 | }); 232 | 233 | return wrapper; 234 | }; 235 | 236 | export const ConnectIssuingCard = ({ 237 | defaultCard, 238 | cardSwitching, 239 | showSpendControls, 240 | fetchEphemeralKey, 241 | onLoadError, 242 | onLoaderStart, 243 | }: { 244 | defaultCard?: string; 245 | cardSwitching?: boolean; 246 | showSpendControls?: boolean; 247 | fetchEphemeralKey?: FetchEphemeralKeyFunction; 248 | } & CommonComponentProps): JSX.Element => { 249 | const {wrapper, component: issuingCard} = useCreateComponent('issuing-card'); 250 | 251 | useUpdateWithSetter(issuingCard, defaultCard, (comp, val) => 252 | comp.setDefaultCard(val) 253 | ); 254 | useUpdateWithSetter(issuingCard, cardSwitching, (comp, val) => 255 | comp.setCardSwitching(val) 256 | ); 257 | useUpdateWithSetter(issuingCard, showSpendControls, (comp, val) => 258 | comp.setShowSpendControls(val) 259 | ); 260 | useUpdateWithSetter(issuingCard, fetchEphemeralKey, (comp, val) => 261 | comp.setFetchEphemeralKey(val) 262 | ); 263 | useUpdateWithSetter(issuingCard, onLoaderStart, (comp, val) => { 264 | comp.setOnLoaderStart(val); 265 | }); 266 | useUpdateWithSetter(issuingCard, onLoadError, (comp, val) => { 267 | comp.setOnLoadError(val); 268 | }); 269 | 270 | return wrapper; 271 | }; 272 | 273 | export const ConnectIssuingCardsList = ({ 274 | showSpendControls, 275 | issuingProgram, 276 | fetchEphemeralKey, 277 | onLoadError, 278 | onLoaderStart, 279 | }: { 280 | showSpendControls?: boolean; 281 | issuingProgram?: string; 282 | fetchEphemeralKey?: FetchEphemeralKeyFunction; 283 | } & CommonComponentProps): JSX.Element => { 284 | const {wrapper, component: issuingCardsList} = 285 | useCreateComponent('issuing-cards-list'); 286 | 287 | useUpdateWithSetter(issuingCardsList, showSpendControls, (comp, val) => 288 | comp.setShowSpendControls(val) 289 | ); 290 | useUpdateWithSetter(issuingCardsList, issuingProgram, (comp, val) => 291 | comp.setIssuingProgram(val) 292 | ); 293 | useUpdateWithSetter(issuingCardsList, fetchEphemeralKey, (comp, val) => 294 | comp.setFetchEphemeralKey(val) 295 | ); 296 | useUpdateWithSetter(issuingCardsList, onLoaderStart, (comp, val) => { 297 | comp.setOnLoaderStart(val); 298 | }); 299 | useUpdateWithSetter(issuingCardsList, onLoadError, (comp, val) => { 300 | comp.setOnLoadError(val); 301 | }); 302 | 303 | return wrapper; 304 | }; 305 | 306 | export const ConnectFinancialAccount = ({ 307 | financialAccount, 308 | onLoadError, 309 | onLoaderStart, 310 | }: { 311 | financialAccount: string; 312 | } & CommonComponentProps): JSX.Element => { 313 | const {wrapper, component: financialAccountComponent} = 314 | useCreateComponent('financial-account'); 315 | 316 | useUpdateWithSetter( 317 | financialAccountComponent, 318 | financialAccount, 319 | (comp, val) => comp.setFinancialAccount(val) 320 | ); 321 | useUpdateWithSetter(financialAccountComponent, onLoaderStart, (comp, val) => { 322 | comp.setOnLoaderStart(val); 323 | }); 324 | useUpdateWithSetter(financialAccountComponent, onLoadError, (comp, val) => { 325 | comp.setOnLoadError(val); 326 | }); 327 | 328 | return wrapper; 329 | }; 330 | 331 | export const ConnectFinancialAccountTransactions = ({ 332 | financialAccount, 333 | onLoadError, 334 | onLoaderStart, 335 | }: { 336 | financialAccount: string; 337 | } & CommonComponentProps): JSX.Element => { 338 | const {wrapper, component: financialAccountTransactionsComponent} = 339 | useCreateComponent('financial-account-transactions'); 340 | 341 | useUpdateWithSetter( 342 | financialAccountTransactionsComponent, 343 | financialAccount, 344 | (comp, val) => comp.setFinancialAccount(val) 345 | ); 346 | useUpdateWithSetter( 347 | financialAccountTransactionsComponent, 348 | onLoaderStart, 349 | (comp, val) => { 350 | comp.setOnLoaderStart(val); 351 | } 352 | ); 353 | useUpdateWithSetter( 354 | financialAccountTransactionsComponent, 355 | onLoadError, 356 | (comp, val) => { 357 | comp.setOnLoadError(val); 358 | } 359 | ); 360 | 361 | return wrapper; 362 | }; 363 | 364 | export const ConnectDocuments = ({ 365 | onLoadError, 366 | onLoaderStart, 367 | }: CommonComponentProps): JSX.Element => { 368 | const {wrapper, component: documents} = useCreateComponent('documents'); 369 | 370 | useUpdateWithSetter(documents, onLoaderStart, (comp, val) => { 371 | comp.setOnLoaderStart(val); 372 | }); 373 | useUpdateWithSetter(documents, onLoadError, (comp, val) => { 374 | comp.setOnLoadError(val); 375 | }); 376 | 377 | return wrapper; 378 | }; 379 | 380 | export const ConnectPayoutsList = ({ 381 | onLoadError, 382 | onLoaderStart, 383 | }: CommonComponentProps): JSX.Element => { 384 | const {wrapper, component: payoutsList} = useCreateComponent('payouts-list'); 385 | 386 | useUpdateWithSetter(payoutsList, onLoaderStart, (comp, val) => { 387 | comp.setOnLoaderStart(val); 388 | }); 389 | useUpdateWithSetter(payoutsList, onLoadError, (comp, val) => { 390 | comp.setOnLoadError(val); 391 | }); 392 | return wrapper; 393 | }; 394 | 395 | export const ConnectBalances = ({ 396 | onLoadError, 397 | onLoaderStart, 398 | }: CommonComponentProps): JSX.Element => { 399 | const {wrapper, component: balances} = useCreateComponent('balances'); 400 | 401 | useUpdateWithSetter(balances, onLoaderStart, (comp, val) => { 402 | comp.setOnLoaderStart(val); 403 | }); 404 | useUpdateWithSetter(balances, onLoadError, (comp, val) => { 405 | comp.setOnLoadError(val); 406 | }); 407 | 408 | return wrapper; 409 | }; 410 | 411 | export const ConnectTaxRegistrations = ({ 412 | onLoadError, 413 | onLoaderStart, 414 | displayCountries, 415 | onAfterTaxRegistrationAdded, 416 | onAfterTaxRegistrationExpired, 417 | }: { 418 | displayCountries?: string[]; 419 | onAfterTaxRegistrationAdded?: ({id}: {id: string}) => void; 420 | onAfterTaxRegistrationExpired?: ({id}: {id: string}) => void; 421 | } & CommonComponentProps): JSX.Element => { 422 | const {wrapper, component: taxRegistrations} = 423 | useCreateComponent('tax-registrations'); 424 | 425 | useUpdateWithSetter(taxRegistrations, onLoaderStart, (comp, val) => { 426 | comp.setOnLoaderStart(val); 427 | }); 428 | 429 | useUpdateWithSetter(taxRegistrations, onLoadError, (comp, val) => { 430 | comp.setOnLoadError(val); 431 | }); 432 | 433 | useUpdateWithSetter(taxRegistrations, displayCountries, (comp, val) => { 434 | comp.setDisplayCountries(val); 435 | }); 436 | 437 | useUpdateWithSetter( 438 | taxRegistrations, 439 | onAfterTaxRegistrationAdded, 440 | (comp, val) => { 441 | comp.setOnAfterTaxRegistrationAdded(val); 442 | } 443 | ); 444 | 445 | useUpdateWithSetter( 446 | taxRegistrations, 447 | onAfterTaxRegistrationExpired, 448 | (comp, val) => { 449 | comp.setOnAfterTaxRegistrationExpired(val); 450 | } 451 | ); 452 | 453 | return wrapper; 454 | }; 455 | 456 | export const ConnectTaxSettings = ({ 457 | onLoadError, 458 | onLoaderStart, 459 | hideProductTaxCodeSelector, 460 | displayHeadOfficeCountries, 461 | onTaxSettingsUpdated, 462 | }: { 463 | hideProductTaxCodeSelector?: boolean; 464 | displayHeadOfficeCountries?: string[]; 465 | onTaxSettingsUpdated?: ({id}: {id: string}) => void; 466 | } & CommonComponentProps): JSX.Element => { 467 | const {wrapper, component: taxSettings} = useCreateComponent('tax-settings'); 468 | 469 | useUpdateWithSetter(taxSettings, onLoaderStart, (comp, val) => { 470 | comp.setOnLoaderStart(val); 471 | }); 472 | useUpdateWithSetter(taxSettings, onLoadError, (comp, val) => { 473 | comp.setOnLoadError(val); 474 | }); 475 | 476 | useUpdateWithSetter(taxSettings, hideProductTaxCodeSelector, (comp, val) => { 477 | comp.setHideProductTaxCodeSelector(val); 478 | }); 479 | 480 | useUpdateWithSetter(taxSettings, displayHeadOfficeCountries, (comp, val) => { 481 | comp.setDisplayHeadOfficeCountries(val); 482 | }); 483 | 484 | useUpdateWithSetter(taxSettings, onTaxSettingsUpdated, (comp, val) => { 485 | comp.setOnTaxSettingsUpdated(val); 486 | }); 487 | 488 | return wrapper; 489 | }; 490 | 491 | export const ConnectInstantPayoutsPromotion = ({ 492 | onInstantPayoutsPromotionLoaded, 493 | onInstantPayoutCreated, 494 | onLoadError, 495 | onLoaderStart, 496 | }: { 497 | onInstantPayoutsPromotionLoaded?: ({ 498 | promotionShown, 499 | }: { 500 | promotionShown: boolean; 501 | }) => void; 502 | onInstantPayoutCreated?: ({payoutId}: {payoutId: string}) => void; 503 | } & CommonComponentProps): JSX.Element => { 504 | const {wrapper, component: instantPayoutsPromotion} = useCreateComponent( 505 | 'instant-payouts-promotion' 506 | ); 507 | 508 | useUpdateWithSetter(instantPayoutsPromotion, onLoaderStart, (comp, val) => { 509 | comp.setOnLoaderStart(val); 510 | }); 511 | useUpdateWithSetter(instantPayoutsPromotion, onLoadError, (comp, val) => { 512 | comp.setOnLoadError(val); 513 | }); 514 | useUpdateWithSetter( 515 | instantPayoutsPromotion, 516 | onInstantPayoutsPromotionLoaded, 517 | (comp, val) => { 518 | comp.setOnInstantPayoutsPromotionLoaded(val); 519 | } 520 | ); 521 | useUpdateWithSetter( 522 | instantPayoutsPromotion, 523 | onInstantPayoutCreated, 524 | (comp, val) => { 525 | comp.setOnInstantPayoutCreated(val); 526 | } 527 | ); 528 | 529 | return wrapper; 530 | }; 531 | 532 | export const ConnectPayoutDetails = ({ 533 | payout, 534 | onClose, 535 | onLoadError, 536 | onLoaderStart, 537 | }: { 538 | /** 539 | * @param payout the ID of `payout` to be displayed. 540 | */ 541 | payout: string; 542 | onClose: () => void; 543 | } & CommonComponentProps): JSX.Element | null => { 544 | const {wrapper, component: payoutDetails} = 545 | useCreateComponent('payout-details'); 546 | 547 | useUpdateWithSetter(payoutDetails, payout, (comp, val) => 548 | comp.setPayout(val) 549 | ); 550 | useUpdateWithSetter(payoutDetails, onClose, (comp, val) => 551 | comp.setOnClose(val) 552 | ); 553 | useUpdateWithSetter(payoutDetails, onLoaderStart, (comp, val) => { 554 | comp.setOnLoaderStart(val); 555 | }); 556 | useUpdateWithSetter(payoutDetails, onLoadError, (comp, val) => { 557 | comp.setOnLoadError(val); 558 | }); 559 | return wrapper; 560 | }; 561 | --------------------------------------------------------------------------------