@quiltt/react 3.9.1 → 3.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +18 -6
- package/dist/{QuilttAuthProvider-client-CqVLC1w4.js → QuilttAuthProvider-client-D6Iok6TS.js} +60 -7
- package/dist/index.d.ts +10 -5
- package/dist/index.js +16 -6
- package/dist/{useQuilttConnector-client-L80c55xy.js → useQuilttConnector-client-BPcRwcNX.js} +2 -2
- package/dist/{useQuilttSession-client-DA9F_3KG.js → useQuilttSession-client-KZRjdl5I.js} +1 -1
- package/package.json +13 -10
- package/src/components/QuilttButton.tsx +34 -8
- package/src/providers/QuilttAuthProvider.tsx +10 -8
- package/src/utils/index.ts +1 -0
- package/src/utils/isDeepEqual.ts +60 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @quiltt/react
|
|
2
2
|
|
|
3
|
+
## 3.9.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#316](https://github.com/quiltt/quiltt-js/pull/316) [`de5d43e`](https://github.com/quiltt/quiltt-js/commit/de5d43e664a8bbb04595816718f5c645a9c3df27) Thanks [@rubendinho](https://github.com/rubendinho)! - Updated `main` param to `package.json` to improve analyzing the package.
|
|
8
|
+
|
|
9
|
+
- [#313](https://github.com/quiltt/quiltt-js/pull/313) [`3b789c9`](https://github.com/quiltt/quiltt-js/commit/3b789c9413ab2f9bdda965248ed7a8ccaf270172) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Update typings for QuilttButton onLoad handler
|
|
10
|
+
|
|
11
|
+
- [#312](https://github.com/quiltt/quiltt-js/pull/312) [`11ba6a3`](https://github.com/quiltt/quiltt-js/commit/11ba6a3af1975349a63640bb99ed0e34ffee3f1c) Thanks [@zubairaziz](https://github.com/zubairaziz)! - Update session handling in QuilttAuthProvider
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [[`de5d43e`](https://github.com/quiltt/quiltt-js/commit/de5d43e664a8bbb04595816718f5c645a9c3df27), [`3b789c9`](https://github.com/quiltt/quiltt-js/commit/3b789c9413ab2f9bdda965248ed7a8ccaf270172), [`11ba6a3`](https://github.com/quiltt/quiltt-js/commit/11ba6a3af1975349a63640bb99ed0e34ffee3f1c)]:
|
|
14
|
+
- @quiltt/core@3.9.2
|
|
15
|
+
|
|
3
16
|
## 3.9.1
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -9,17 +9,27 @@ See the guides [here](https://www.quiltt.dev/connector/sdks/react).
|
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
12
|
+
With `npm`:
|
|
13
|
+
|
|
12
14
|
```shell
|
|
13
15
|
$ npm install @quiltt/react
|
|
14
|
-
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
With `yarn`:
|
|
19
|
+
|
|
20
|
+
```shell
|
|
15
21
|
$ yarn add @quiltt/react
|
|
16
|
-
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
With `pnpm`:
|
|
25
|
+
|
|
26
|
+
```shell
|
|
17
27
|
$ pnpm add @quiltt/react
|
|
18
28
|
```
|
|
19
29
|
|
|
20
30
|
## Core Modules and Types
|
|
21
31
|
|
|
22
|
-
The `@quiltt/react` library ships with `@quiltt/core`, which provides an
|
|
32
|
+
The `@quiltt/react` library ships with `@quiltt/core`, which provides an API clients and essential functionality for building Javascript-based applications with Quiltt. See the [Core README](../core/README.md) for more information.
|
|
23
33
|
|
|
24
34
|
## React Components
|
|
25
35
|
|
|
@@ -38,10 +48,11 @@ By default, the rendered component will be a `<button>` element, but you can sup
|
|
|
38
48
|
```tsx
|
|
39
49
|
import { useState } from 'react'
|
|
40
50
|
import { QuilttButton } from '@quiltt/react'
|
|
51
|
+
import type { ConnectorSDKCallbackMetadata } from '@quiltt/react'
|
|
41
52
|
|
|
42
53
|
export const App = () => {
|
|
43
54
|
const [connectionId, setConnectionId] = useState<string>()
|
|
44
|
-
const handleExitSuccess = (metadata) => {
|
|
55
|
+
const handleExitSuccess = (metadata: ConnectorSDKCallbackMetadata) => {
|
|
45
56
|
setConnectionId(metadata?.connectionId)
|
|
46
57
|
}
|
|
47
58
|
|
|
@@ -71,10 +82,11 @@ By default, the rendered component will be a `<div>` element, but you can supply
|
|
|
71
82
|
```tsx
|
|
72
83
|
import { useState } from 'react'
|
|
73
84
|
import { QuilttContainer } from '@quiltt/react'
|
|
85
|
+
import type { ConnectorSDKCallbackMetadata } from '@quiltt/react'
|
|
74
86
|
|
|
75
87
|
export const App = () => {
|
|
76
88
|
const [connectionId, setConnectionId] = useState<string>()
|
|
77
|
-
const handleExitSuccess = (metadata) => {
|
|
89
|
+
const handleExitSuccess = (metadata: ConnectorSDKCallbackMetadata) => {
|
|
78
90
|
setConnectionId(metadata?.connectionId)
|
|
79
91
|
}
|
|
80
92
|
|
|
@@ -184,7 +196,7 @@ export default App
|
|
|
184
196
|
|
|
185
197
|
## Typescript support
|
|
186
198
|
|
|
187
|
-
`@quiltt/react` is written in Typescript and ships with its own type definitions
|
|
199
|
+
`@quiltt/react` is written in Typescript and ships with its own type definitions, as well as the type definitions from `@quiltt/core`.
|
|
188
200
|
|
|
189
201
|
## License
|
|
190
202
|
|
package/dist/{QuilttAuthProvider-client-CqVLC1w4.js → QuilttAuthProvider-client-D6Iok6TS.js}
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useCallback, useMemo, useEffect } from 'react';
|
|
2
|
+
import { useCallback, useRef, useMemo, useEffect } from 'react';
|
|
3
3
|
import { JsonWebTokenParse, QuilttClient, InMemoryCache } from '@quiltt/core';
|
|
4
4
|
import '@apollo/client/react/hooks/useApolloClient.js';
|
|
5
5
|
import './useQuilttSettings-client-DU_Qfc8X.js';
|
|
6
6
|
import './useSession-client-CG5lGS9F.js';
|
|
7
7
|
import { jsx } from 'react/jsx-runtime';
|
|
8
8
|
import { ApolloProvider } from '@apollo/client/react/context/ApolloProvider.js';
|
|
9
|
-
import { u as useQuilttSession } from './useQuilttSession-client-
|
|
9
|
+
import { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
|
|
10
10
|
|
|
11
11
|
const useIdentifySession = (auth, setSession)=>{
|
|
12
12
|
const identifySession = useCallback(async (payload, callbacks)=>{
|
|
@@ -102,14 +102,64 @@ const useRevokeSession = (auth, session, setSession)=>{
|
|
|
102
102
|
return revokeSession;
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Performs a deep equality comparison between two values
|
|
107
|
+
*
|
|
108
|
+
* This function recursively compares all properties to determine if they are equal.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }) // true
|
|
113
|
+
* isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } }) // false
|
|
114
|
+
* ```
|
|
115
|
+
*/ const isDeepEqual = (obj1, obj2)=>{
|
|
116
|
+
// Handle primitive types and null/undefined
|
|
117
|
+
if (obj1 === obj2) return true;
|
|
118
|
+
if (obj1 === null || obj2 === null || typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
|
|
119
|
+
// Handle special object types
|
|
120
|
+
if (obj1 instanceof Date && obj2 instanceof Date) {
|
|
121
|
+
return obj1.getTime() === obj2.getTime();
|
|
122
|
+
}
|
|
123
|
+
if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
|
|
124
|
+
return obj1.toString() === obj2.toString();
|
|
125
|
+
}
|
|
126
|
+
if (obj1 instanceof Map && obj2 instanceof Map) {
|
|
127
|
+
if (obj1.size !== obj2.size) return false;
|
|
128
|
+
for (const [key, value] of obj1){
|
|
129
|
+
if (!obj2.has(key) || !isDeepEqual(value, obj2.get(key))) return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
if (obj1 instanceof Set && obj2 instanceof Set) {
|
|
134
|
+
if (obj1.size !== obj2.size) return false;
|
|
135
|
+
for (const item of obj1){
|
|
136
|
+
if (!Array.from(obj2).some((value)=>isDeepEqual(item, value))) return false;
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
// Handle arrays
|
|
141
|
+
if (Array.isArray(obj1) && Array.isArray(obj2)) {
|
|
142
|
+
if (obj1.length !== obj2.length) return false;
|
|
143
|
+
return obj1.every((value, index)=>isDeepEqual(value, obj2[index]));
|
|
144
|
+
}
|
|
145
|
+
// If one is array and other isn't, they're not equal
|
|
146
|
+
if (Array.isArray(obj1) || Array.isArray(obj2)) return false;
|
|
147
|
+
const keys1 = Object.keys(obj1);
|
|
148
|
+
const keys2 = Object.keys(obj2);
|
|
149
|
+
if (keys1.length !== keys2.length) return false;
|
|
150
|
+
return keys1.every((key)=>{
|
|
151
|
+
return Object.prototype.hasOwnProperty.call(obj2, key) && isDeepEqual(obj1[key], obj2[key]);
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
105
155
|
/**
|
|
106
156
|
* If a token is provided, will validate the token against the api and then import
|
|
107
157
|
* it into trusted storage. While this process is happening, the component is put
|
|
108
158
|
* into a loading state and the children are not rendered to prevent race conditions
|
|
109
159
|
* from triggering within the transitionary state.
|
|
110
|
-
*
|
|
111
160
|
*/ const QuilttAuthProvider = ({ token, children })=>{
|
|
112
161
|
const { session, importSession } = useQuilttSession();
|
|
162
|
+
const previousSessionRef = useRef(session);
|
|
113
163
|
// @todo: extract into a provider so it can accessed by child components
|
|
114
164
|
const graphQLClient = useMemo(()=>new QuilttClient({
|
|
115
165
|
cache: new InMemoryCache()
|
|
@@ -121,12 +171,15 @@ const useRevokeSession = (auth, session, setSession)=>{
|
|
|
121
171
|
token,
|
|
122
172
|
importSession
|
|
123
173
|
]);
|
|
124
|
-
// Reset Client Store when
|
|
125
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: We should reset the store whenever the session changes
|
|
174
|
+
// Reset Client Store when session changes (using deep comparison)
|
|
126
175
|
useEffect(()=>{
|
|
127
|
-
|
|
176
|
+
if (!isDeepEqual(session, previousSessionRef.current)) {
|
|
177
|
+
graphQLClient.resetStore();
|
|
178
|
+
previousSessionRef.current = session;
|
|
179
|
+
}
|
|
128
180
|
}, [
|
|
129
|
-
session
|
|
181
|
+
session,
|
|
182
|
+
graphQLClient
|
|
130
183
|
]);
|
|
131
184
|
return /*#__PURE__*/ jsx(ApolloProvider, {
|
|
132
185
|
client: graphQLClient,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Maybe, QuilttJWT, UsernamePayload, UnprocessableData, AuthAPI, PasscodePayload, ConnectorSDKConnectorOptions, ConnectorSDKCallbacks } from '@quiltt/core';
|
|
2
2
|
export * from '@quiltt/core';
|
|
3
3
|
import * as react from 'react';
|
|
4
|
-
import { RefObject, useLayoutEffect, Dispatch, SetStateAction, FC, PropsWithChildren, Component, ComponentType } from 'react';
|
|
4
|
+
import { RefObject, useLayoutEffect, Dispatch, SetStateAction, FC, PropsWithChildren, Component, ComponentType, MouseEvent } from 'react';
|
|
5
5
|
import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient.js';
|
|
6
6
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
7
7
|
|
|
@@ -115,7 +115,6 @@ type QuilttAuthProviderProps = PropsWithChildren & {
|
|
|
115
115
|
* it into trusted storage. While this process is happening, the component is put
|
|
116
116
|
* into a loading state and the children are not rendered to prevent race conditions
|
|
117
117
|
* from triggering within the transitionary state.
|
|
118
|
-
*
|
|
119
118
|
*/
|
|
120
119
|
declare const QuilttAuthProvider: FC<QuilttAuthProviderProps>;
|
|
121
120
|
|
|
@@ -136,13 +135,19 @@ declare const QuilttSettingsProvider: FC<QuilttSettingsProviderProps>;
|
|
|
136
135
|
type AnyTag = string | FC<any> | (new (props: any) => Component);
|
|
137
136
|
type PropsOf<Tag> = Tag extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[Tag] : Tag extends ComponentType<infer Props> ? Props & JSX.IntrinsicAttributes : never;
|
|
138
137
|
|
|
139
|
-
type
|
|
138
|
+
type BaseQuilttButtonProps<T extends AnyTag> = {
|
|
140
139
|
as?: T;
|
|
141
140
|
connectorId: string;
|
|
142
141
|
connectionId?: string;
|
|
143
142
|
institution?: string;
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
onClick?: (event: MouseEvent<HTMLElement>) => void;
|
|
144
|
+
};
|
|
145
|
+
type QuilttCallbackProps = Omit<ConnectorSDKCallbacks, 'onLoad'> & {
|
|
146
|
+
onLoad?: ConnectorSDKCallbacks['onLoad'];
|
|
147
|
+
onHtmlLoad?: React.ReactEventHandler<HTMLElement>;
|
|
148
|
+
};
|
|
149
|
+
type QuilttButtonProps<T extends AnyTag> = PropsWithChildren<BaseQuilttButtonProps<T> & QuilttCallbackProps>;
|
|
150
|
+
declare const QuilttButton: <T extends AnyTag = "button">({ as, connectorId, connectionId, institution, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, onClick, onHtmlLoad, children, ...props }: QuilttButtonProps<T> & PropsOf<T>) => react_jsx_runtime.JSX.Element;
|
|
146
151
|
|
|
147
152
|
type QuilttContainerProps<T extends AnyTag> = PropsWithChildren<{
|
|
148
153
|
as?: T;
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export * from '@quiltt/core';
|
|
2
2
|
export { u as useEventListener } from './useEventListener-client-DVM5xwKY.js';
|
|
3
3
|
export { u as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect-client-DeTHOKz1.js';
|
|
4
|
-
import { Q as QuilttAuthProvider } from './QuilttAuthProvider-client-
|
|
5
|
-
export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-
|
|
4
|
+
import { Q as QuilttAuthProvider } from './QuilttAuthProvider-client-D6Iok6TS.js';
|
|
5
|
+
export { b as useAuthenticateSession, a as useIdentifySession, u as useImportSession, c as useRevokeSession } from './QuilttAuthProvider-client-D6Iok6TS.js';
|
|
6
6
|
export { u as useQuilttClient } from './useQuilttClient-client-CAAUait1.js';
|
|
7
|
-
import { u as useQuilttConnector } from './useQuilttConnector-client-
|
|
8
|
-
export { u as useQuilttSession } from './useQuilttSession-client-
|
|
7
|
+
import { u as useQuilttConnector } from './useQuilttConnector-client-BPcRwcNX.js';
|
|
8
|
+
export { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
|
|
9
9
|
export { Q as QuilttSettings, u as useQuilttSettings } from './useQuilttSettings-client-DU_Qfc8X.js';
|
|
10
10
|
export { u as useSession } from './useSession-client-CG5lGS9F.js';
|
|
11
11
|
export { u as useStorage } from './useStorage-client-B3keU-oI.js';
|
|
@@ -22,7 +22,7 @@ const QuilttProvider = ({ clientId, token, children })=>{
|
|
|
22
22
|
});
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, children, ...props })=>{
|
|
25
|
+
const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onOpen, onLoad, onExit, onExitSuccess, onExitAbort, onExitError, onClick, onHtmlLoad, children, ...props })=>{
|
|
26
26
|
const { open } = useQuilttConnector(connectorId, {
|
|
27
27
|
connectionId,
|
|
28
28
|
institution,
|
|
@@ -35,8 +35,18 @@ const QuilttButton = ({ as, connectorId, connectionId, institution, onEvent, onO
|
|
|
35
35
|
onExitError
|
|
36
36
|
});
|
|
37
37
|
const Button = as || 'button';
|
|
38
|
+
const handleClick = (event)=>{
|
|
39
|
+
// Call the user's onClick handler first to allow for:
|
|
40
|
+
// 1. Pre-open validation
|
|
41
|
+
// 2. Preventing opening via event.preventDefault()
|
|
42
|
+
// 3. Setting up state before connector opens
|
|
43
|
+
if (onClick) onClick(event);
|
|
44
|
+
// Then open the Quiltt connector
|
|
45
|
+
open();
|
|
46
|
+
};
|
|
38
47
|
return /*#__PURE__*/ jsx(Button, {
|
|
39
|
-
onClick:
|
|
48
|
+
onClick: handleClick,
|
|
49
|
+
onLoad: onHtmlLoad,
|
|
40
50
|
"quiltt-connection": connectionId,
|
|
41
51
|
...props,
|
|
42
52
|
children: children
|
package/dist/{useQuilttConnector-client-L80c55xy.js → useQuilttConnector-client-BPcRwcNX.js}
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { cdnBase } from '@quiltt/core';
|
|
4
|
-
import { u as useQuilttSession } from './useQuilttSession-client-
|
|
4
|
+
import { u as useQuilttSession } from './useQuilttSession-client-KZRjdl5I.js';
|
|
5
5
|
import { u as useScript } from './useScript-client-Cx5nb9RW.js';
|
|
6
6
|
|
|
7
|
-
var version = "3.9.
|
|
7
|
+
var version = "3.9.2";
|
|
8
8
|
|
|
9
9
|
const useQuilttConnector = (connectorId, options)=>{
|
|
10
10
|
const status = useScript(`${cdnBase}/v1/connector.js?agent=react-${version}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
3
|
import { AuthAPI } from '@quiltt/core';
|
|
4
|
-
import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-client-
|
|
4
|
+
import { u as useImportSession, a as useIdentifySession, b as useAuthenticateSession, c as useRevokeSession } from './QuilttAuthProvider-client-D6Iok6TS.js';
|
|
5
5
|
import { u as useQuilttSettings } from './useQuilttSettings-client-DU_Qfc8X.js';
|
|
6
6
|
import { u as useSession } from './useSession-client-CG5lGS9F.js';
|
|
7
7
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quiltt/react",
|
|
3
|
-
"version": "3.9.
|
|
4
|
-
"description": "React
|
|
3
|
+
"version": "3.9.2",
|
|
4
|
+
"description": "React Components and Hooks for Quiltt Connector",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"quiltt",
|
|
7
7
|
"quiltt-connector",
|
|
@@ -15,12 +15,10 @@
|
|
|
15
15
|
"directory": "packages/react"
|
|
16
16
|
},
|
|
17
17
|
"license": "MIT",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"./src/hooks/useStorage.ts"
|
|
23
|
-
],
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/quiltt/quiltt-js/issues"
|
|
20
|
+
},
|
|
21
|
+
"sideEffects": [],
|
|
24
22
|
"type": "module",
|
|
25
23
|
"exports": {
|
|
26
24
|
".": {
|
|
@@ -34,9 +32,10 @@
|
|
|
34
32
|
"src/**",
|
|
35
33
|
"CHANGELOG.md"
|
|
36
34
|
],
|
|
35
|
+
"main": "dist/index.js",
|
|
37
36
|
"dependencies": {
|
|
38
37
|
"@apollo/client": "^3.11.8",
|
|
39
|
-
"@quiltt/core": "3.9.
|
|
38
|
+
"@quiltt/core": "3.9.2"
|
|
40
39
|
},
|
|
41
40
|
"devDependencies": {
|
|
42
41
|
"@biomejs/biome": "1.9.4",
|
|
@@ -47,12 +46,16 @@
|
|
|
47
46
|
"react": "18.3.1",
|
|
48
47
|
"react-dom": "18.3.1",
|
|
49
48
|
"rimraf": "6.0.1",
|
|
50
|
-
"typescript": "5.
|
|
49
|
+
"typescript": "5.7.2"
|
|
51
50
|
},
|
|
52
51
|
"peerDependencies": {
|
|
53
52
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
54
53
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
55
54
|
},
|
|
55
|
+
"tags": [
|
|
56
|
+
"quiltt",
|
|
57
|
+
"react"
|
|
58
|
+
],
|
|
56
59
|
"publishConfig": {
|
|
57
60
|
"access": "public"
|
|
58
61
|
},
|
|
@@ -1,17 +1,30 @@
|
|
|
1
|
-
import type { PropsWithChildren } from 'react'
|
|
1
|
+
import type { MouseEvent, PropsWithChildren } from 'react'
|
|
2
2
|
|
|
3
3
|
import type { ConnectorSDKCallbacks } from '@quiltt/core'
|
|
4
4
|
|
|
5
5
|
import { useQuilttConnector } from '../hooks/useQuilttConnector'
|
|
6
6
|
import type { AnyTag, PropsOf } from '../types'
|
|
7
7
|
|
|
8
|
+
// Base button props without callback-specific properties
|
|
9
|
+
type BaseQuilttButtonProps<T extends AnyTag> = {
|
|
10
|
+
as?: T
|
|
11
|
+
connectorId: string
|
|
12
|
+
connectionId?: string // For Reconnect Mode
|
|
13
|
+
institution?: string // For Connect Mode
|
|
14
|
+
// Override the native onClick handler
|
|
15
|
+
onClick?: (event: MouseEvent<HTMLElement>) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Separate SDK callback types
|
|
19
|
+
type QuilttCallbackProps = Omit<ConnectorSDKCallbacks, 'onLoad'> & {
|
|
20
|
+
// Separate the SDK onLoad from the HTML onLoad to avoid conflicts
|
|
21
|
+
onLoad?: ConnectorSDKCallbacks['onLoad'] // Handles SDK initialization
|
|
22
|
+
onHtmlLoad?: React.ReactEventHandler<HTMLElement> // Handles DOM element load
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Combined type for the full component
|
|
8
26
|
type QuilttButtonProps<T extends AnyTag> = PropsWithChildren<
|
|
9
|
-
|
|
10
|
-
as?: T
|
|
11
|
-
connectorId: string
|
|
12
|
-
connectionId?: string // For Reconnect Mode
|
|
13
|
-
institution?: string // For Connect Mode
|
|
14
|
-
} & ConnectorSDKCallbacks
|
|
27
|
+
BaseQuilttButtonProps<T> & QuilttCallbackProps
|
|
15
28
|
>
|
|
16
29
|
|
|
17
30
|
export const QuilttButton = <T extends AnyTag = 'button'>({
|
|
@@ -26,6 +39,8 @@ export const QuilttButton = <T extends AnyTag = 'button'>({
|
|
|
26
39
|
onExitSuccess,
|
|
27
40
|
onExitAbort,
|
|
28
41
|
onExitError,
|
|
42
|
+
onClick,
|
|
43
|
+
onHtmlLoad,
|
|
29
44
|
children,
|
|
30
45
|
...props
|
|
31
46
|
}: QuilttButtonProps<T> & PropsOf<T>) => {
|
|
@@ -43,8 +58,19 @@ export const QuilttButton = <T extends AnyTag = 'button'>({
|
|
|
43
58
|
|
|
44
59
|
const Button = as || 'button'
|
|
45
60
|
|
|
61
|
+
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
|
62
|
+
// Call the user's onClick handler first to allow for:
|
|
63
|
+
// 1. Pre-open validation
|
|
64
|
+
// 2. Preventing opening via event.preventDefault()
|
|
65
|
+
// 3. Setting up state before connector opens
|
|
66
|
+
if (onClick) onClick(event)
|
|
67
|
+
|
|
68
|
+
// Then open the Quiltt connector
|
|
69
|
+
open()
|
|
70
|
+
}
|
|
71
|
+
|
|
46
72
|
return (
|
|
47
|
-
<Button onClick={
|
|
73
|
+
<Button onClick={handleClick} onLoad={onHtmlLoad} quiltt-connection={connectionId} {...props}>
|
|
48
74
|
{children}
|
|
49
75
|
</Button>
|
|
50
76
|
)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useEffect, useMemo, useRef } from 'react'
|
|
3
4
|
import type { FC, PropsWithChildren } from 'react'
|
|
4
|
-
import { useEffect, useMemo } from 'react'
|
|
5
5
|
|
|
6
6
|
import { ApolloProvider } from '@apollo/client/react/context/ApolloProvider.js'
|
|
7
|
-
|
|
8
7
|
import { InMemoryCache, QuilttClient } from '@quiltt/core'
|
|
9
8
|
|
|
10
|
-
import { useQuilttSession } from '
|
|
9
|
+
import { useQuilttSession } from '@/hooks'
|
|
10
|
+
import { isDeepEqual } from '@/utils'
|
|
11
11
|
|
|
12
12
|
type QuilttAuthProviderProps = PropsWithChildren & {
|
|
13
13
|
/** The Session token obtained from the server */
|
|
@@ -19,10 +19,10 @@ type QuilttAuthProviderProps = PropsWithChildren & {
|
|
|
19
19
|
* it into trusted storage. While this process is happening, the component is put
|
|
20
20
|
* into a loading state and the children are not rendered to prevent race conditions
|
|
21
21
|
* from triggering within the transitionary state.
|
|
22
|
-
*
|
|
23
22
|
*/
|
|
24
23
|
export const QuilttAuthProvider: FC<QuilttAuthProviderProps> = ({ token, children }) => {
|
|
25
24
|
const { session, importSession } = useQuilttSession()
|
|
25
|
+
const previousSessionRef = useRef(session)
|
|
26
26
|
|
|
27
27
|
// @todo: extract into a provider so it can accessed by child components
|
|
28
28
|
const graphQLClient = useMemo(
|
|
@@ -38,11 +38,13 @@ export const QuilttAuthProvider: FC<QuilttAuthProviderProps> = ({ token, childre
|
|
|
38
38
|
if (token) importSession(token)
|
|
39
39
|
}, [token, importSession])
|
|
40
40
|
|
|
41
|
-
// Reset Client Store when
|
|
42
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: We should reset the store whenever the session changes
|
|
41
|
+
// Reset Client Store when session changes (using deep comparison)
|
|
43
42
|
useEffect(() => {
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
if (!isDeepEqual(session, previousSessionRef.current)) {
|
|
44
|
+
graphQLClient.resetStore()
|
|
45
|
+
previousSessionRef.current = session
|
|
46
|
+
}
|
|
47
|
+
}, [session, graphQLClient])
|
|
46
48
|
|
|
47
49
|
return <ApolloProvider client={graphQLClient}>{children}</ApolloProvider>
|
|
48
50
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './isDeepEqual'
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performs a deep equality comparison between two values
|
|
3
|
+
*
|
|
4
|
+
* This function recursively compares all properties to determine if they are equal.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } }) // true
|
|
9
|
+
* isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } }) // false
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export const isDeepEqual = (obj1: unknown, obj2: unknown): boolean => {
|
|
13
|
+
// Handle primitive types and null/undefined
|
|
14
|
+
if (obj1 === obj2) return true
|
|
15
|
+
if (obj1 === null || obj2 === null || typeof obj1 !== 'object' || typeof obj2 !== 'object')
|
|
16
|
+
return false
|
|
17
|
+
|
|
18
|
+
// Handle special object types
|
|
19
|
+
if (obj1 instanceof Date && obj2 instanceof Date) {
|
|
20
|
+
return obj1.getTime() === obj2.getTime()
|
|
21
|
+
}
|
|
22
|
+
if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
|
|
23
|
+
return obj1.toString() === obj2.toString()
|
|
24
|
+
}
|
|
25
|
+
if (obj1 instanceof Map && obj2 instanceof Map) {
|
|
26
|
+
if (obj1.size !== obj2.size) return false
|
|
27
|
+
for (const [key, value] of obj1) {
|
|
28
|
+
if (!obj2.has(key) || !isDeepEqual(value, obj2.get(key))) return false
|
|
29
|
+
}
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
if (obj1 instanceof Set && obj2 instanceof Set) {
|
|
33
|
+
if (obj1.size !== obj2.size) return false
|
|
34
|
+
for (const item of obj1) {
|
|
35
|
+
if (!Array.from(obj2).some((value) => isDeepEqual(item, value))) return false
|
|
36
|
+
}
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Handle arrays
|
|
41
|
+
if (Array.isArray(obj1) && Array.isArray(obj2)) {
|
|
42
|
+
if (obj1.length !== obj2.length) return false
|
|
43
|
+
return obj1.every((value, index) => isDeepEqual(value, obj2[index]))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// If one is array and other isn't, they're not equal
|
|
47
|
+
if (Array.isArray(obj1) || Array.isArray(obj2)) return false
|
|
48
|
+
|
|
49
|
+
const keys1 = Object.keys(obj1)
|
|
50
|
+
const keys2 = Object.keys(obj2)
|
|
51
|
+
|
|
52
|
+
if (keys1.length !== keys2.length) return false
|
|
53
|
+
|
|
54
|
+
return keys1.every((key) => {
|
|
55
|
+
return (
|
|
56
|
+
Object.prototype.hasOwnProperty.call(obj2, key) &&
|
|
57
|
+
isDeepEqual((obj1 as any)[key], (obj2 as any)[key])
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
}
|