@@ -10,13 +10,15 @@ import { Popover } from '@actual-app/components/popover';
10
10
import { Text } from '@actual-app/components/text' ;
11
11
import { View } from '@actual-app/components/view' ;
12
12
13
- import { pushModal } from 'loot-core/client/actions' ;
13
+ import { addNotification , pushModal } from 'loot-core/client/actions' ;
14
14
import { send } from 'loot-core/platform/client/fetch' ;
15
15
16
16
import { useAuth } from '../../auth/AuthProvider' ;
17
17
import { Permissions } from '../../auth/types' ;
18
18
import { authorizeBank } from '../../gocardless' ;
19
+ import { useFeatureFlag } from '../../hooks/useFeatureFlag' ;
19
20
import { useGoCardlessStatus } from '../../hooks/useGoCardlessStatus' ;
21
+ import { usePluggyAiStatus } from '../../hooks/usePluggyAiStatus' ;
20
22
import { useSimpleFinStatus } from '../../hooks/useSimpleFinStatus' ;
21
23
import { useSyncServerStatus } from '../../hooks/useSyncServerStatus' ;
22
24
import { SvgDotsHorizontalTriple } from '../../icons/v1' ;
@@ -34,6 +36,8 @@ type CreateAccountProps = {
34
36
export function CreateAccountModal ( { upgradingAccountId } : CreateAccountProps ) {
35
37
const { t } = useTranslation ( ) ;
36
38
39
+ const isPluggyAiEnabled = useFeatureFlag ( 'pluggyAiBankSync' ) ;
40
+
37
41
const syncServerStatus = useSyncServerStatus ( ) ;
38
42
const dispatch = useDispatch ( ) ;
39
43
const [ isGoCardlessSetupComplete , setIsGoCardlessSetupComplete ] = useState <
@@ -42,6 +46,9 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
42
46
const [ isSimpleFinSetupComplete , setIsSimpleFinSetupComplete ] = useState <
43
47
boolean | null
44
48
> ( null ) ;
49
+ const [ isPluggyAiSetupComplete , setIsPluggyAiSetupComplete ] = useState <
50
+ boolean | null
51
+ > ( null ) ;
45
52
const { hasPermission } = useAuth ( ) ;
46
53
const multiuserEnabled = useMultiuserEnabled ( ) ;
47
54
@@ -118,6 +125,70 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
118
125
setLoadingSimpleFinAccounts ( false ) ;
119
126
} ;
120
127
128
+ const onConnectPluggyAi = async ( ) => {
129
+ if ( ! isPluggyAiSetupComplete ) {
130
+ onPluggyAiInit ( ) ;
131
+ return ;
132
+ }
133
+
134
+ try {
135
+ const results = await send ( 'pluggyai-accounts' ) ;
136
+ if ( results . error_code ) {
137
+ throw new Error ( results . reason ) ;
138
+ } else if ( 'error' in results ) {
139
+ throw new Error ( results . error ) ;
140
+ }
141
+
142
+ const newAccounts = [ ] ;
143
+
144
+ type NormalizedAccount = {
145
+ account_id : string ;
146
+ name : string ;
147
+ institution : string ;
148
+ orgDomain : string | null ;
149
+ orgId : string ;
150
+ balance : number ;
151
+ } ;
152
+
153
+ for ( const oldAccount of results . accounts ) {
154
+ const newAccount : NormalizedAccount = {
155
+ account_id : oldAccount . id ,
156
+ name : `${ oldAccount . name . trim ( ) } - ${ oldAccount . type === 'BANK' ? oldAccount . taxNumber : oldAccount . owner } ` ,
157
+ institution : oldAccount . name ,
158
+ orgDomain : null ,
159
+ orgId : oldAccount . id ,
160
+ balance :
161
+ oldAccount . type === 'BANK'
162
+ ? oldAccount . bankData . automaticallyInvestedBalance +
163
+ oldAccount . bankData . closingBalance
164
+ : oldAccount . balance ,
165
+ } ;
166
+
167
+ newAccounts . push ( newAccount ) ;
168
+ }
169
+
170
+ dispatch (
171
+ pushModal ( 'select-linked-accounts' , {
172
+ accounts : newAccounts ,
173
+ syncSource : 'pluggyai' ,
174
+ } ) ,
175
+ ) ;
176
+ } catch ( err ) {
177
+ console . error ( err ) ;
178
+ addNotification ( {
179
+ type : 'error' ,
180
+ title : t ( 'Error when trying to contact Pluggy.ai' ) ,
181
+ message : ( err as Error ) . message ,
182
+ timeout : 5000 ,
183
+ } ) ;
184
+ dispatch (
185
+ pushModal ( 'pluggyai-init' , {
186
+ onSuccess : ( ) => setIsPluggyAiSetupComplete ( true ) ,
187
+ } ) ,
188
+ ) ;
189
+ }
190
+ } ;
191
+
121
192
const onGoCardlessInit = ( ) => {
122
193
dispatch (
123
194
pushModal ( 'gocardless-init' , {
@@ -134,6 +205,14 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
134
205
) ;
135
206
} ;
136
207
208
+ const onPluggyAiInit = ( ) => {
209
+ dispatch (
210
+ pushModal ( 'pluggyai-init' , {
211
+ onSuccess : ( ) => setIsPluggyAiSetupComplete ( true ) ,
212
+ } ) ,
213
+ ) ;
214
+ } ;
215
+
137
216
const onGoCardlessReset = ( ) => {
138
217
send ( 'secret-set' , {
139
218
name : 'gocardless_secretId' ,
@@ -162,6 +241,25 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
162
241
} ) ;
163
242
} ;
164
243
244
+ const onPluggyAiReset = ( ) => {
245
+ send ( 'secret-set' , {
246
+ name : 'pluggyai_clientId' ,
247
+ value : null ,
248
+ } ) . then ( ( ) => {
249
+ send ( 'secret-set' , {
250
+ name : 'pluggyai_clientSecret' ,
251
+ value : null ,
252
+ } ) . then ( ( ) => {
253
+ send ( 'secret-set' , {
254
+ name : 'pluggyai_itemIds' ,
255
+ value : null ,
256
+ } ) . then ( ( ) => {
257
+ setIsPluggyAiSetupComplete ( false ) ;
258
+ } ) ;
259
+ } ) ;
260
+ } ) ;
261
+ } ;
262
+
165
263
const onCreateLocalAccount = ( ) => {
166
264
dispatch ( pushModal ( 'add-local-account' ) ) ;
167
265
} ;
@@ -176,6 +274,11 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
176
274
setIsSimpleFinSetupComplete ( configuredSimpleFin ) ;
177
275
} , [ configuredSimpleFin ] ) ;
178
276
277
+ const { configuredPluggyAi } = usePluggyAiStatus ( ) ;
278
+ useEffect ( ( ) => {
279
+ setIsPluggyAiSetupComplete ( configuredPluggyAi ) ;
280
+ } , [ configuredPluggyAi ] ) ;
281
+
179
282
let title = t ( 'Add account' ) ;
180
283
const [ loadingSimpleFinAccounts , setLoadingSimpleFinAccounts ] =
181
284
useState ( false ) ;
@@ -359,9 +462,77 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
359
462
hundreds of banks.
360
463
</ Trans >
361
464
</ Text >
465
+ { isPluggyAiEnabled && (
466
+ < >
467
+ < View
468
+ style = { {
469
+ flexDirection : 'row' ,
470
+ gap : 10 ,
471
+ alignItems : 'center' ,
472
+ } }
473
+ >
474
+ < ButtonWithLoading
475
+ isDisabled = { syncServerStatus !== 'online' }
476
+ style = { {
477
+ padding : '10px 0' ,
478
+ fontSize : 15 ,
479
+ fontWeight : 600 ,
480
+ flex : 1 ,
481
+ } }
482
+ onPress = { onConnectPluggyAi }
483
+ >
484
+ { isPluggyAiSetupComplete
485
+ ? t ( 'Link bank account with Pluggy.ai' )
486
+ : t ( 'Set up Pluggy.ai for bank sync' ) }
487
+ </ ButtonWithLoading >
488
+ { isPluggyAiSetupComplete && (
489
+ < DialogTrigger >
490
+ < Button
491
+ variant = "bare"
492
+ aria-label = { t ( 'Pluggy.ai menu' ) }
493
+ >
494
+ < SvgDotsHorizontalTriple
495
+ width = { 15 }
496
+ height = { 15 }
497
+ style = { { transform : 'rotateZ(90deg)' } }
498
+ />
499
+ </ Button >
500
+
501
+ < Popover >
502
+ < Menu
503
+ onMenuSelect = { item => {
504
+ if ( item === 'reconfigure' ) {
505
+ onPluggyAiReset ( ) ;
506
+ }
507
+ } }
508
+ items = { [
509
+ {
510
+ name : 'reconfigure' ,
511
+ text : t ( 'Reset Pluggy.ai credentials' ) ,
512
+ } ,
513
+ ] }
514
+ />
515
+ </ Popover >
516
+ </ DialogTrigger >
517
+ ) }
518
+ </ View >
519
+ < Text style = { { lineHeight : '1.4em' , fontSize : 15 } } >
520
+ < Trans >
521
+ < strong >
522
+ Link a < em > Brazilian</ em > bank account
523
+ </ strong > { ' ' }
524
+ to automatically download transactions. Pluggy.ai
525
+ provides reliable, up-to-date information from
526
+ hundreds of banks.
527
+ </ Trans >
528
+ </ Text >
529
+ </ >
530
+ ) }
362
531
</ >
363
532
) }
364
- { ( ! isGoCardlessSetupComplete || ! isSimpleFinSetupComplete ) &&
533
+ { ( ! isGoCardlessSetupComplete ||
534
+ ! isSimpleFinSetupComplete ||
535
+ ! isPluggyAiSetupComplete ) &&
365
536
! canSetSecrets && (
366
537
< Warning >
367
538
< Trans >
@@ -371,6 +542,7 @@ export function CreateAccountModal({ upgradingAccountId }: CreateAccountProps) {
371
542
{ [
372
543
isGoCardlessSetupComplete ? '' : 'GoCardless' ,
373
544
isSimpleFinSetupComplete ? '' : 'SimpleFin' ,
545
+ isPluggyAiSetupComplete ? '' : 'Pluggy.ai' ,
374
546
]
375
547
. filter ( Boolean )
376
548
. join ( ' or ' ) }
0 commit comments