saafe-redirection-flow 2.2.0 → 2.2.1

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.
@@ -7,7 +7,7 @@ import { Button } from '@/components/ui/button'
7
7
  import { Checkbox } from '@/components/ui/checkbox'
8
8
  import { MobileFooter } from '@/components/ui/mobile-footer'
9
9
  import WebFooter from '@/components/ui/web-footer'
10
- import { useFipStore } from '@/store/fip.store'
10
+ import { FipData, useFipStore } from '@/store/fip.store'
11
11
  import { AlertCircle, PlusIcon } from 'lucide-react'
12
12
  import { useNavigate } from 'react-router-dom'
13
13
  import {
@@ -30,6 +30,10 @@ import { accountService } from '@/services/api'
30
30
  import { useQuery } from '@tanstack/react-query'
31
31
  import { BankAccount } from '@/pages/accounts/OldUser'
32
32
  import AutoDiscoveryLoader from '@/components/AutoDiscoveryLoader'
33
+ import removeUnderscores from '@/utils/removeUnderscore'
34
+ import { extractCategoriesFromResponse, useFipQuery } from '@/hooks/use-fip-query'
35
+ import { fipService } from '@/services/api/fip.service'
36
+
33
37
 
34
38
  interface LinkedAccount extends BankAccount {
35
39
  fipName: string;
@@ -60,17 +64,23 @@ const AccountsToProceed = ({
60
64
  activeCategory,
61
65
  setSelectedAccountToLink,
62
66
  setOriginalAccounts,
63
- originalAccounts
67
+ originalAccounts,
68
+ setIdentifiers,
64
69
  } = useFipStore()
65
70
  const fipIds = state?.selectedFips || selectedFips[category] || []
66
71
  useSetPageTitle(`Select accounts you want to link`)
67
72
  const [discoveryResult, setDiscoveryResult] =
68
73
  useState<ProcessedDiscoveryResult | null>(null)
74
+ console.log("🚀 ~ discoveryResult:", discoveryResult)
69
75
  const [selectedAccounts, setSelectedAccounts] = useState<string[]>([])
76
+ const [linkedAccountCount, setLinkedAccountCount] = useState<number>(0)
77
+ console.log("🚀 ~ selectedAccounts:", selectedAccounts)
70
78
  const [isLoading, setIsLoading] = useState(false)
71
79
  const { decodedInfo } = useRedirectStore()
80
+ console.log("🚀 ~ decodedInfo:", decodedInfo)
72
81
  const isUserPresent = decodedInfo?.isUserPresent || false
73
-
82
+ console.log("🚀 ~ isUserPresent:", isUserPresent)
83
+
74
84
  // State for collapsible UI
75
85
  const [openItemId, setOpenItemId] = useState<number | null>(0)
76
86
  const [tabOpen, setTabOpen] = useState(false)
@@ -79,7 +89,6 @@ const AccountsToProceed = ({
79
89
  // Account discovery with enhanced error handling
80
90
  const { mutate, errorMessage, clearError } = useAccountDiscovery()
81
91
  const autoDiscovery = useAutoDiscovery()
82
-
83
92
  // Fetch linked accounts for returning users
84
93
  const consentHandle = decodedInfo?.srcref || '';
85
94
  const getLinkedAccountsList = useQuery({
@@ -98,12 +107,13 @@ const AccountsToProceed = ({
98
107
  }))
99
108
  }
100
109
  })
101
-
110
+
102
111
  // Process linked accounts for display
103
112
  const activeLinkedAccount = React.useMemo(() => {
104
113
  if (!getLinkedAccountsList?.data) return {};
105
-
114
+
106
115
  const accounts: LinkedAccount[] = getLinkedAccountsList.data;
116
+ setLinkedAccountCount(accounts?.length)
107
117
  return accounts.reduce((acc: Record<string, LinkedAccount[]>, account) => {
108
118
  const fiType = account.fiType || 'Other';
109
119
  if (!acc[fiType]) {
@@ -113,34 +123,34 @@ const AccountsToProceed = ({
113
123
  return acc;
114
124
  }, {});
115
125
  }, [getLinkedAccountsList?.data]);
116
-
126
+
117
127
  // Count selected accounts for UI
118
128
  const selectedFIPCount = React.useMemo(() => {
119
129
  if (!discoveryResult?.groupedAccounts) return { totalCount: 0, groupedCount: {} };
120
-
130
+
121
131
  const groupedCount: Record<string, number> = {};
122
132
  let totalCount = 0;
123
-
133
+
124
134
  Object.entries(discoveryResult.groupedAccounts).forEach(([fiType, accounts]) => {
125
- const selectedInType = accounts.filter(account =>
135
+ const selectedInType = accounts.filter(account =>
126
136
  selectedAccounts.includes(account.id)
127
137
  ).length;
128
138
  groupedCount[fiType] = selectedInType;
129
139
  totalCount += selectedInType;
130
140
  });
131
-
141
+
132
142
  return { totalCount, groupedCount };
133
143
  }, [discoveryResult?.groupedAccounts, selectedAccounts]);
134
-
144
+
135
145
  // Count linked accounts for UI
136
146
  const linkedAccountsCount = React.useMemo(() => {
137
147
  if (!activeLinkedAccount) return { totalCount: 0 };
138
-
148
+
139
149
  const totalCount = Object.values(activeLinkedAccount as Record<string, LinkedAccount[]>)
140
150
  .flat()
141
151
  .filter(account => selectedAccounts.includes(account.accountRefNumber))
142
152
  .length;
143
-
153
+
144
154
  return { totalCount };
145
155
  }, [activeLinkedAccount, selectedAccounts]);
146
156
 
@@ -164,7 +174,7 @@ const AccountsToProceed = ({
164
174
 
165
175
  // Check if "Discover More" should be shown based on auto discovery configuration
166
176
  const autoDiscoveryConfig = parseAutoDiscoveryConfig(decodedInfo?.customization?.otherAppCustomization)
167
- const shouldShowDiscoverMore = state?.isAutoDiscovery
177
+ const shouldShowDiscoverMore = state?.isAutoDiscovery
168
178
  ? autoDiscoveryConfig[activeCategory?.toUpperCase() as keyof typeof autoDiscoveryConfig]?.showDiscoverMore
169
179
  : true // Always show for manual discovery
170
180
 
@@ -180,163 +190,157 @@ const AccountsToProceed = ({
180
190
 
181
191
  // Discover accounts when component loads
182
192
  useEffect(() => {
183
- console.log('AccountsToProceed useEffect:', {
184
- isAutoDiscovery: state?.isAutoDiscovery,
185
- category,
186
- fipIds: fipIds.length,
187
- groupedFipsKeys: Object.keys(groupedFips || {})
188
- });
189
-
190
- let mounted = true
193
+ let mounted = true;
191
194
 
192
- if (state?.isAutoDiscovery) {
193
- console.log('Using AUTO DISCOVERY for category:', category);
194
-
195
- // First validate identifiers before proceeding with auto discovery
196
- const { fips, identifiers: storeIdentifiers } = useFipStore.getState();
197
- const identifiersList: Array<{
198
- type: string;
199
- value: string | undefined;
200
- category: string;
201
- }> = [];
202
-
203
- // For auto discovery, we need to check identifiers across all FIPs in the category
204
- const categoryFips = fips.filter(fip =>
205
- fip.fiTypeList.some(fiType =>
206
- fiTypeCategoryMap[category]?.includes(fiType)
207
- ) && fip.isEnabled && fip.isOnBoarded
208
- );
209
-
210
- // Collect required identifiers from category FIPs
211
- categoryFips.forEach(fip => {
212
- fip.Identifiers?.forEach(identifier => {
213
- if (!identifiersList.find(i => i.type === identifier.type)) {
214
- // First check if we already have this identifier in store
215
- const storeIdentifier = storeIdentifiers.find(i => i.type === identifier.type);
216
- let value: string | undefined = undefined;
217
- if (storeIdentifier?.value !== undefined) {
218
- value = storeIdentifier.value;
219
- }
220
-
221
- // If not in store, fall back to decodedInfo
222
- if (!value) {
223
- value = identifier.type === 'PAN'
224
- ? (decodedInfo?.pan || undefined)
225
- : identifier.type === 'MOBILE'
226
- ? (decodedInfo?.phoneNumber || undefined)
227
- : identifier.type === 'DOB'
228
- ? (decodedInfo?.dob || undefined)
229
- : undefined;
195
+ const fetchAndProceed = async () => {
196
+ let { fips, identifiers: storeIdentifiers } = useFipStore.getState();
197
+ const { decodedInfo } = useRedirectStore.getState();
198
+
199
+ if (fips.length === 0) {
200
+ console.log('🔄 No FIPs in store, fetching now...');
201
+ try {
202
+ const response = await fipService.getRawFipResponse();
203
+ const categories = extractCategoriesFromResponse(response);
204
+ const categorizedFips: Record<string, FipData[]> = {};
205
+
206
+ categories.forEach(category => {
207
+ const categoryData = response[category];
208
+ if (Array.isArray(categoryData)) {
209
+ categorizedFips[category] = categoryData as FipData[];
230
210
  }
231
-
232
- identifiersList.push({
233
- type: identifier.type,
234
- value,
235
- category: identifier.category
236
- });
237
- }
238
- });
239
- });
211
+ });
212
+
213
+ const allFips = Object.values(categorizedFips).flat();
240
214
 
241
- console.log('Auto Discovery - Checking identifiers:', identifiersList);
242
- console.log('Auto Discovery - Store identifiers:', storeIdentifiers);
243
-
244
- // Check if any required identifiers are missing
245
- const missingIdentifiers = identifiersList.filter(identifier =>
246
- !identifier.value || identifier.value.trim() === ''
247
- );
248
-
249
- if (missingIdentifiers.length > 0) {
250
- console.log('Auto Discovery - Missing identifiers found, redirecting to collect:', missingIdentifiers);
251
- // Redirect to DiscoverAccount to collect missing identifiers
252
- navigate(`/link-accounts/discover-account`, {
253
- state: {
254
- category,
255
- selectedFips: fipIds, // Use the auto discovery FIP IDs
256
- fromAutoDiscovery: true
215
+ if (allFips.length > 0) {
216
+ useFipStore.getState().setFips(
217
+ allFips,
218
+ decodedInfo?.fiTypesRequiredForConsent,
219
+ categorizedFips
220
+ );
221
+ fips = allFips;
222
+ } else {
223
+ console.error("No FIPs returned after fetch");
224
+ return;
257
225
  }
258
- });
259
- return; // Don't proceed with auto discovery
226
+ } catch (err) {
227
+ console.error("Failed to fetch FIPs:", err);
228
+ return;
229
+ }
260
230
  }
261
231
 
262
- console.log('Auto Discovery - All identifiers available, proceeding with discovery');
263
- // All identifiers are available, set them in store and proceed with auto discovery
264
- const { setIdentifiers } = useFipStore.getState();
265
- setIdentifiers(identifiersList);
266
-
267
- // Use auto discovery
268
- setIsLoading(true)
269
- autoDiscovery.clearError()
232
+ // Auto-Discovery
233
+ console.log('AccountsToProceed useEffect:', {
234
+ isAutoDiscovery: state?.isAutoDiscovery,
235
+ category,
236
+ fipIds: fipIds.length,
237
+ groupedFipsKeys: Object.keys(groupedFips || {})
238
+ });
270
239
 
271
- autoDiscovery.mutate(category, {
272
- onSuccess: result => {
273
- if (!mounted) return
274
- console.log('Auto discovery success:', result);
275
- const groupedAccounts = result.accounts.reduce(
276
- (acc: Record<string, typeof result.accounts>, account) => {
277
- const key = account.type
278
- if (!acc[key]) {
279
- acc[key] = []
240
+ if (state?.isAutoDiscovery) {
241
+ console.log('Using AUTO DISCOVERY for category:', category);
242
+ const identifiersList: Array<{ type: string; value: string | undefined; category: string }> = [];
243
+ const categoryFips = fips.filter(fip =>
244
+ fip.fiTypeList.some(fiType =>
245
+ fiTypeCategoryMap[category]?.includes(fiType)
246
+ ) && fip.isEnabled && fip.isOnBoarded
247
+ );
248
+ categoryFips.forEach(fip => {
249
+ fip.Identifiers?.forEach(identifier => {
250
+ if (!identifiersList.find(i => i.type === identifier.type)) {
251
+ const storeIdentifier = storeIdentifiers.find(i => i.type === identifier.type);
252
+ let value = storeIdentifier?.value;
253
+ if (!value) {
254
+ value = identifier.type === 'PAN'
255
+ ? (decodedInfo?.pan || undefined)
256
+ : identifier.type === 'MOBILE'
257
+ ? (decodedInfo?.phoneNumber || undefined)
258
+ : identifier.type === 'DOB'
259
+ ? (decodedInfo?.dob || undefined)
260
+ : undefined;
280
261
  }
281
- acc[key].push(account)
282
- return acc
283
- },
284
- {}
285
- )
286
- setDiscoveryResult({ ...result, groupedAccounts })
287
- setOriginalAccounts(result.originalAccounts)
262
+ identifiersList.push({ type: identifier.type, value, category: identifier.category });
263
+ }
264
+ });
265
+ });
288
266
 
289
- setIsLoading(false)
290
- },
291
- onError: (error) => {
292
- if (!mounted) return
293
- console.log('Auto discovery error:', error);
294
- setIsLoading(false)
267
+ const missingIdentifiers = identifiersList.filter(identifier =>
268
+ !identifier.value || identifier.value.trim() === ''
269
+ );
270
+
271
+ if (missingIdentifiers.length > 0) {
272
+ navigate(`/link-accounts/discover-account`, {
273
+ state: {
274
+ category,
275
+ selectedFips: fipIds,
276
+ fromAutoDiscovery: true
277
+ }
278
+ });
279
+ return;
295
280
  }
296
- })
297
- } else if (fipIds.length > 0) {
298
- console.log('Using MANUAL DISCOVERY for FIPs:', fipIds);
299
- // Use manual discovery
300
- setIsLoading(true)
301
- clearError()
302
281
 
303
- mutate(fipIds, {
304
- onSuccess: result => {
305
- if (!mounted) return
306
- const groupedAccounts = result.accounts.reduce(
307
- (acc: Record<string, typeof result.accounts>, account) => {
308
- const key = account.type
309
- if (!acc[key]) {
310
- acc[key] = []
311
- }
312
- acc[key].push(account)
313
- return acc
314
- },
315
- {}
316
- )
317
- setDiscoveryResult({ ...result, groupedAccounts })
318
- setOriginalAccounts(result.originalAccounts)
282
+ setIdentifiers(identifiersList);
283
+ setIsLoading(true);
284
+ autoDiscovery.clearError();
319
285
 
320
- setIsLoading(false)
321
- },
322
- onError: () => {
323
- if (!mounted) return
324
- setIsLoading(false)
325
- }
326
- })
327
- } else {
328
- console.log('No discovery triggered - no FIPs and not auto discovery');
329
- }
286
+ autoDiscovery.mutate(category, {
287
+ onSuccess: result => {
288
+ if (!mounted) return;
289
+ const groupedAccounts = result.accounts.reduce((acc, account) => {
290
+ const key = account.type;
291
+ if (!acc[key]) acc[key] = [];
292
+ acc[key].push(account);
293
+ return acc;
294
+ }, {} as Record<string, typeof result.accounts>);
295
+
296
+ setDiscoveryResult({ ...result, groupedAccounts });
297
+ setOriginalAccounts(result.originalAccounts);
298
+ setIsLoading(false);
299
+ },
300
+ onError: (error) => {
301
+ if (!mounted) return;
302
+ console.error('Auto discovery error:', error);
303
+ setIsLoading(false);
304
+ }
305
+ });
306
+ } else if (fipIds.length > 0) {
307
+ setIsLoading(true);
308
+ clearError();
309
+
310
+ mutate(fipIds, {
311
+ onSuccess: result => {
312
+ if (!mounted) return;
313
+ const groupedAccounts = result.accounts.reduce((acc, account) => {
314
+ const key = account.type;
315
+ if (!acc[key]) acc[key] = [];
316
+ acc[key].push(account);
317
+ return acc;
318
+ }, {} as Record<string, typeof result.accounts>);
319
+
320
+ setDiscoveryResult({ ...result, groupedAccounts });
321
+ setOriginalAccounts(result.originalAccounts);
322
+ setIsLoading(false);
323
+ },
324
+ onError: () => {
325
+ if (!mounted) return;
326
+ setIsLoading(false);
327
+ }
328
+ });
329
+ } else {
330
+ console.log('No discovery triggered - no FIPs and not auto discovery');
331
+ }
332
+ };
333
+
334
+ fetchAndProceed();
330
335
 
331
- // Cleanup function to handle unmounting
332
336
  return () => {
333
- mounted = false
334
- }
335
- }, [fipIds, state?.isAutoDiscovery, category]) // Depend on auto discovery flag and category
337
+ mounted = false;
338
+ };
339
+ }, [fipIds, state?.isAutoDiscovery, category]);
336
340
 
337
341
  const handleProceed = () => {
338
342
  console.log('handleProceed called with:', { selectedAccounts, originalAccounts })
339
-
343
+
340
344
  const result: Array<{
341
345
  id: string;
342
346
  fipId: string;
@@ -347,24 +351,24 @@ const AccountsToProceed = ({
347
351
  bankName: string;
348
352
  logoUrl?: string;
349
353
  isNew?: boolean;
350
- DiscoveredAccounts: Array<{
351
- FIType: string;
352
- accType: string;
353
- accRefNumber: string;
354
- maskedAccNumber: string;
355
- state: string | null;
356
- amcName: string | null;
357
- logoUrl: string | null;
358
- }>;
354
+ DiscoveredAccounts: Array<{
355
+ FIType: string;
356
+ accType: string;
357
+ accRefNumber: string;
358
+ maskedAccNumber: string;
359
+ state: string | null;
360
+ amcName: string | null;
361
+ logoUrl: string | null;
362
+ }>;
359
363
  }> = []
360
-
364
+
361
365
  originalAccounts.forEach((item, index) => {
362
- const data = item?.["DiscoveredAccounts"].filter(account =>
366
+ const data = item?.["DiscoveredAccounts"].filter(account =>
363
367
  selectedAccounts.includes(account.accRefNumber)
364
368
  )
365
369
  if (data.length > 0) {
366
370
  // For each FIP with selected accounts, create one entry
367
- result.push({
371
+ result.push({
368
372
  id: `${index} - ${item.fipName}`,
369
373
  fipId: item.fipId,
370
374
  fipName: item.fipName,
@@ -382,7 +386,7 @@ const AccountsToProceed = ({
382
386
  console.log('handleProceed result:', result)
383
387
  console.log('Setting selectedAccountToLink with result length:', result.length)
384
388
  setSelectedAccountToLink(result)
385
-
389
+
386
390
  // Navigate to account linking screen with selected accounts and signature
387
391
  navigate('/link-accounts/link', {
388
392
  state: {
@@ -407,8 +411,13 @@ const AccountsToProceed = ({
407
411
  return updatedSelection;
408
412
  });
409
413
  };
410
-
411
- const toggleBankSelection = (bankId: string) => {
414
+
415
+ const toggleBankSelection = (bankId: string, event?: React.MouseEvent) => {
416
+
417
+ if (event) {
418
+ event.stopPropagation();
419
+ }
420
+
412
421
  setSelectedAccounts(prev => {
413
422
  let updatedSelection;
414
423
  if (prev.includes(bankId)) {
@@ -505,17 +514,17 @@ const AccountsToProceed = ({
505
514
  const validateIdentifiersBeforeDiscoverMore = () => {
506
515
  // Get the FIP store state to access fips data
507
516
  const { fips } = useFipStore.getState();
508
-
517
+
509
518
  if (!fipIds || fipIds.length === 0) {
510
519
  return true; // No FIPs selected, can't validate
511
520
  }
512
-
521
+
513
522
  const identifiersList: Array<{
514
523
  type: string;
515
524
  value: string | null | undefined;
516
525
  categoryType: string;
517
526
  }> = [];
518
-
527
+
519
528
  // Collect required identifiers from selected FIPs
520
529
  fips
521
530
  .filter(fip => fipIds.includes(fip.id))
@@ -528,12 +537,12 @@ const AccountsToProceed = ({
528
537
  : identifier.type === 'MOBILE'
529
538
  ? decodedInfo?.phoneNumber
530
539
  : identifier.type === 'DOB'
531
- ? decodedInfo?.dob
532
- : null;
533
- identifiersList.push({
540
+ ? decodedInfo?.dob
541
+ : null;
542
+ identifiersList.push({
534
543
  type: identifier.type,
535
- value,
536
- categoryType: identifier.category
544
+ value,
545
+ categoryType: identifier.category
537
546
  });
538
547
  }
539
548
  });
@@ -542,7 +551,7 @@ const AccountsToProceed = ({
542
551
  console.log('AccountsToProceed - Checking identifiers for discover more:', identifiersList);
543
552
 
544
553
  // Check if any required identifiers are missing
545
- const missingIdentifiers = identifiersList.filter(identifier =>
554
+ const missingIdentifiers = identifiersList.filter(identifier =>
546
555
  !identifier.value || identifier.value.trim() === ''
547
556
  );
548
557
 
@@ -591,85 +600,161 @@ const AccountsToProceed = ({
591
600
  ))
592
601
  }
593
602
 
594
- if (
603
+ const hasDiscoveredAccounts =
595
604
  discoveryResult?.groupedAccounts &&
596
- Object.keys(discoveryResult.groupedAccounts).length > 0
597
- ) {
598
- return (
599
- <div className='mt-2 shadow-sm shadow-black/5 rounded-md'>
600
- <div className='py-5 px-5 rounded-t-md text-primary border-primary bg-ring dark:text-primary'>
601
- <h1 className='flex justify-start items-center gap-1 text-md md:text-xl font-semibold'>
602
- {"Discovered for you"}
603
- <Sparkles size={20} fill={'#008e9a'} />
604
- </h1>
605
- <p className='text-sm md:text-lg'>
606
- {selectedFIPCount.totalCount > 0
607
- ? `${selectedFIPCount.totalCount} of ${discoveryResult?.accounts?.length || 0} accounts selected`
608
- : `No accounts selected`}
609
- </p>
610
- </div>
611
- <div className='flex flex-col rounded-b-2xl bg-card'>
612
- {Object.entries(discoveryResult.groupedAccounts as Record<string, any[]>).map(([fiType, accounts], index) => {
613
- const isOpen = openItemId === index;
614
-
615
- return (
616
- <Collapsible
617
- key={fiType}
618
- open={isOpen}
619
- onOpenChange={() => setOpenItemId(isOpen ? null : index)}
620
- >
621
- <CollapsibleTrigger className="flex justify-between w-full items-center p-4">
622
- <SectionTitle
623
- title={fiType}
624
- rightSection={(
625
- <ChevronDownIcon
626
- size={20}
627
- className={`h-5 w-5 transition-transform duration-300 ${
628
- isOpen ? "rotate-180" : "rotate-0"
629
- }`}
605
+ Object.keys(discoveryResult.groupedAccounts).length > 0;
606
+
607
+ return (
608
+ <>
609
+ {/* Discovered Accounts Section */}
610
+ {hasDiscoveredAccounts && (
611
+ <div>
612
+ <div className='mt-2 shadow-sm shadow-black/5 rounded-md'>
613
+ <div className='py-5 px-5 rounded-t-md text-primary border-primary bg-ring dark:text-primary'>
614
+ <h1 className='flex justify-start items-center gap-1 text-md font-semibold'>
615
+ {"Discovered for you"}
616
+ <Sparkles size={18} fill={'#008e9a'} />
617
+ </h1>
618
+ <p className='text-sm md:text-lg'>
619
+ {selectedFIPCount.totalCount > 0
620
+ ? `${selectedFIPCount.totalCount} of ${discoveryResult?.accounts?.length || 0} accounts selected`
621
+ : `No accounts selected`}
622
+ </p>
623
+ </div>
624
+ <div className='flex flex-col rounded-b-2xl bg-card'>
625
+ {Object.entries(discoveryResult.groupedAccounts as Record<string, any[]>).map(([fiType, accounts], index) => {
626
+ const isOpen = openItemId === index;
627
+
628
+ return (
629
+ <Collapsible
630
+ key={fiType}
631
+ open={isOpen}
632
+ onOpenChange={() => setOpenItemId(isOpen ? null : index)}
633
+ >
634
+ <CollapsibleTrigger className="flex justify-between w-full items-center p-4">
635
+ <SectionTitle
636
+ title={fiType}
637
+ className='text-sm md:text-sm'
638
+ rightSection={(
639
+ <ChevronDownIcon
640
+ size={20}
641
+ className={`h-5 w-5 transition-transform duration-300 ${isOpen ? "rotate-180" : "rotate-0"
642
+ }`}
643
+ />
644
+ )}
645
+ />
646
+ </CollapsibleTrigger>
647
+ <CollapsibleContent open={isOpen} className="px-4 pb-4">
648
+ {accounts.map(account => (
649
+ <div className='mt-2 flex flex-col gap-2' key={account.id}>
650
+ <OuterCard
651
+ selected={selectedAccounts.includes(account.id)}
652
+ onSelect={() => toggleBankSelection(account.id)}
653
+ >
654
+ <BankCard
655
+ bankName={account.bankName}
656
+ image={account.logoUrl}
657
+ subText={`${account.type} | **${formatAccountNumber(account.maskedAccountNumber || "0")}`}
658
+ onClick={() => toggleBankSelection(account.id)}
659
+ rightSection={
660
+ <Checkbox
661
+ id={`checkbox-${account.id}`}
662
+ checked={selectedAccounts.includes(account.id)}
663
+ onCheckedChange={() => toggleBankSelection(account.id)}
630
664
  />
631
- )}
665
+ }
632
666
  />
633
- </CollapsibleTrigger>
634
- <CollapsibleContent open={isOpen} className="px-4 pb-4">
635
- {accounts.map(account => (
636
- <div className='mt-2 flex flex-col gap-2' key={account.id}>
637
- <OuterCard
638
- selected={selectedAccounts.includes(account.id)}
639
- onSelect={() => toggleBankSelection(account.id)}
640
- >
641
- <BankCard
642
- bankName={account.bankName}
643
- image={account.logoUrl}
644
- subText={`${account.type} | **${formatAccountNumber(account.maskedAccountNumber || "0")}`}
645
- onClick={() => toggleBankSelection(account.id)}
646
- rightSection={
647
- <Checkbox
648
- id={`checkbox-${account.id}`}
649
- checked={selectedAccounts.includes(account.id)}
650
- onCheckedChange={() => toggleBankSelection(account.id)}
651
- />
652
- }
653
- />
654
- </OuterCard>
655
- </div>
656
- ))}
657
- </CollapsibleContent>
658
- </Collapsible>
659
- );
660
- })}
661
- </div>
662
- </div>
663
- )
664
- }
667
+ </OuterCard>
668
+ </div>
669
+ ))}
670
+ </CollapsibleContent>
671
+ </Collapsible>
672
+ );
673
+ })}
674
+ </div>
675
+ </div>
676
+ </div>
677
+ )}
665
678
 
666
- return (
667
- <div className='text-center p-4 text-gray-500'>
668
- {(errorMessage || autoDiscovery.errorMessage)
669
- ? 'Failed to discover accounts'
670
- : 'No accounts found for the selected banks'}
671
- </div>
672
- )
679
+ {/* Linked Accounts Section - Always show */}
680
+ <div className='flex flex-col rounded-2xl bg-card mt-6'>
681
+ <Collapsible open={tabOpen} onOpenChange={setTabOpen} className="border-none">
682
+ <CollapsibleTrigger asChild>
683
+ <div className='flex items-center bg-card py-4 px-5 pr-4 rounded-xl justify-between gap-2 cursor-pointer shadow-sm shadow-black/5'>
684
+ <div className='flex flex-col justify-center relative'>
685
+ <p className="text-md text-stone-500 dark:text-gray-200 font-semibold ">Linked accounts</p>
686
+ <p className="text-sm text-stone-700 dark:text-gray-400">
687
+ {linkedAccountCount || 0} linked account{linkedAccountCount > 1 ? "s" : ""} across {Object.keys(activeLinkedAccount).length} FI Type{Object.keys(activeLinkedAccount).length > 1 ? "s" : ""}
688
+ </p>
689
+ </div>
690
+ <div>
691
+ <ChevronDownIcon
692
+ size={18}
693
+ className={`h-5 w-5 transition-transform duration-300 ${tabOpen ? "rotate-180" : "rotate-0"
694
+ }`}
695
+ />
696
+ </div>
697
+ </div>
698
+ </CollapsibleTrigger>
699
+ <CollapsibleContent open={tabOpen} className="pb-4 bg-card rounded-b-xl">
700
+ {Object.entries(activeLinkedAccount as Record<string, LinkedAccount[]>).map(([f, accounts], index) => {
701
+ const isOpen = openLinkAcc === index;
702
+ const fiType = removeUnderscores(f) || f
703
+ return (
704
+ <Collapsible
705
+ key={fiType}
706
+ open={isOpen}
707
+ onOpenChange={() => setOpenLinkAcc(isOpen ? null : index)}
708
+ >
709
+ <CollapsibleTrigger className="flex justify-between items-center p-4 w-[100%]">
710
+ <SectionTitle
711
+ title={fiType}
712
+ className='text-sm md:text-sm'
713
+ rightSection={(
714
+ <ChevronDownIcon
715
+ size={18}
716
+ className={`h-5 w-5 transition-transform duration-300 ${isOpen ? "rotate-180" : "rotate-0"
717
+ }`}
718
+ />
719
+ )}
720
+ />
721
+ </CollapsibleTrigger>
722
+ <CollapsibleContent open={isOpen} className="px-4 pb-4">
723
+ {accounts.map(account => (
724
+ <div className='mt-2 flex flex-col gap-2' key={account.id}>
725
+ <OuterCard
726
+ selected={true}
727
+ >
728
+ <BankCard
729
+ bankName={account.bankName}
730
+ image={account.logoUrl}
731
+ subText={`${account.type} | **${formatAccountNumber(account.maskedAccountNumber || "0")}`}
732
+ />
733
+ </OuterCard>
734
+ </div>
735
+ ))}
736
+ </CollapsibleContent>
737
+ </Collapsible>
738
+ );
739
+ })}
740
+ </CollapsibleContent>
741
+ </Collapsible>
742
+ </div>
743
+
744
+ {/* No discovered accounts message */}
745
+ {!hasDiscoveredAccounts && (
746
+ <Alert className='flex items-center mt-2'>
747
+ <AlertCircle className='w-4 h-4' />
748
+ <AlertDescription className='flex items-center justify-between'>
749
+ {/* {"Do not refresh the page while we fetch your accounts."} */}
750
+ {(errorMessage || autoDiscovery.errorMessage)
751
+ ? 'Failed to discover accounts'
752
+ : 'No new accounts discovered, kindly skip'}
753
+ </AlertDescription>
754
+ </Alert>
755
+ )}
756
+ </>
757
+ );
673
758
  }
674
759
 
675
760
  return (
@@ -684,22 +769,24 @@ const AccountsToProceed = ({
684
769
  )} ${discoveryResult?.accounts?.length === 1 ? t('keywords.account') : t('keywords.accounts')}`}
685
770
 
686
771
  rightSection={
687
- <AnimatedButton
688
- variant={'ghost'}
689
- onClick={handleSelectAll}
690
- disabled={
691
- !discoveryResult?.accounts ||
692
- discoveryResult.accounts.length === 0
693
- }
694
- >
695
- <p className='text-primary'>
696
- {discoveryResult?.accounts &&
697
- selectedAccounts.length === discoveryResult.accounts.length &&
698
- discoveryResult.accounts.length > 0
699
- ? t('unselectAll')
700
- : t('selectAll')}
701
- </p>
702
- </AnimatedButton>
772
+ discoveryResult?.accounts && discoveryResult.accounts.length > 0 ? (
773
+ <AnimatedButton
774
+ variant={'ghost'}
775
+ onClick={handleSelectAll}
776
+ disabled={
777
+ !discoveryResult?.accounts ||
778
+ discoveryResult.accounts.length === 0
779
+ }
780
+ >
781
+ <p className='text-primary'>
782
+ {discoveryResult?.accounts &&
783
+ selectedAccounts.length === discoveryResult.accounts.length &&
784
+ discoveryResult.accounts.length > 0
785
+ ? t('unselectAll')
786
+ : t('selectAll')}
787
+ </p>
788
+ </AnimatedButton>
789
+ ) : null
703
790
  }
704
791
  />
705
792
 
@@ -708,169 +795,187 @@ const AccountsToProceed = ({
708
795
  <AlertCircle className='w-4 h-4' />
709
796
  <AlertDescription className='flex items-center justify-between'>
710
797
  {errorMessage || autoDiscovery.errorMessage}
711
- <Button
712
- variant='outline'
713
- size='sm'
714
- onClick={retryDiscovery}
715
- className='ml-2'
716
- >
717
- Retry
718
- </Button>
798
+ {
799
+ !isLoading && (
800
+ <Button
801
+ variant='outline'
802
+ size='sm'
803
+ onClick={retryDiscovery}
804
+ className='ml-2'
805
+ >
806
+ Retry
807
+ </Button>
808
+ )
809
+ }
810
+ </AlertDescription>
811
+ </Alert>
812
+ )}
813
+
814
+ {isLoading && (
815
+ <Alert className='flex items-center mb-2'>
816
+ <AlertCircle className='w-4 h-4' />
817
+ <AlertDescription className='flex items-center justify-between'>
818
+ {"Do not refresh the page while we fetch your accounts."}
819
+
719
820
  </AlertDescription>
720
821
  </Alert>
721
822
  )}
722
823
 
723
824
  <div className='flex flex-col gap-2'>
724
825
  <div className='flex flex-col gap-2 mb-4'>
725
- {state?.isAutoDiscovery && discoveryResult?.groupedAccounts ? (
726
- <>
727
- {/* Auto Discovery Section */}
728
- <div className='mt-2 shadow-sm shadow-black/5 rounded-xl'>
729
- <div className='py-5 px-5 rounded-t-xl text-primary border-primary bg-ring dark:text-primary'>
730
- <h1 className='flex justify-start items-center gap-1 text-md md:text-xl font-semibold'>
731
- {"Discovered for you"}
732
- <Sparkles size={20} fill={'#008e9a'} />
733
- </h1>
734
- <p className='text-sm md:text-lg'>
735
- {selectedFIPCount.totalCount > 0
736
- ? `${selectedFIPCount.totalCount} of ${discoveryResult?.accounts?.length || 0} accounts selected`
737
- : `No accounts selected`}
738
- </p>
739
- </div>
740
- <div className='flex flex-col rounded-b-2xl bg-card'>
741
- {Object.entries(discoveryResult.groupedAccounts as Record<string, any[]>).map(([fiType, accounts], index) => {
742
- const isOpen = openItemId === index;
743
-
744
- return (
745
- <Collapsible
746
- key={fiType}
747
- open={isOpen}
748
- onOpenChange={() => setOpenItemId(isOpen ? null : index)}
749
- >
750
- <CollapsibleTrigger className="flex justify-between w-full items-center p-4">
751
- <SectionTitle
752
- title={fiType}
753
- rightSection={(
754
- <ChevronDownIcon
755
- size={20}
756
- className={`h-5 w-5 transition-transform duration-300 ${
757
- isOpen ? "rotate-180" : "rotate-0"
826
+ {state?.isAutoDiscovery && discoveryResult?.groupedAccounts ? (
827
+ <>
828
+ {/* Auto Discovery Section */}
829
+ <div className='mt-2 shadow-sm shadow-black/5 rounded-xl'>
830
+ <div className='py-5 px-5 rounded-t-xl text-primary border-primary bg-ring dark:text-primary'>
831
+ <h1 className='flex justify-start items-center gap-1 text-md font-semibold'>
832
+ {"Discovered for you"}
833
+ <Sparkles size={18} fill={'#008e9a'} />
834
+ </h1>
835
+ <p className='text-sm'>
836
+ {selectedFIPCount.totalCount > 0
837
+ ? `${selectedFIPCount.totalCount} of ${discoveryResult?.accounts?.length || 0} accounts selected`
838
+ : `No accounts selected`}
839
+ </p>
840
+ </div>
841
+ <div className='flex flex-col rounded-b-2xl bg-card'>
842
+ {Object.entries(discoveryResult.groupedAccounts as Record<string, any[]>).map(([f, accounts], index) => {
843
+ const isOpen = openItemId === index;
844
+ const fiType = removeUnderscores(f)
845
+
846
+ return (
847
+ <Collapsible
848
+ key={fiType}
849
+ open={isOpen}
850
+ onOpenChange={() => setOpenItemId(isOpen ? null : index)}
851
+ >
852
+ <CollapsibleTrigger className="flex justify-between w-full items-center p-4">
853
+ <SectionTitle
854
+ className='text-sm md:text-sm'
855
+ title={fiType}
856
+ rightSection={(
857
+ <ChevronDownIcon
858
+ size={18}
859
+ className={`h-5 w-5 transition-transform duration-300 ${isOpen ? "rotate-180" : "rotate-0"
758
860
  }`}
759
- />
760
- )}
761
- />
762
- </CollapsibleTrigger>
763
- <CollapsibleContent open={isOpen} className="px-4 pb-4">
764
- {accounts.map(account => (
765
- <div className='mt-2 flex flex-col gap-2' key={account.id}>
766
- <OuterCard
767
- selected={selectedAccounts.includes(account.id)}
768
- onSelect={() => toggleBankSelection(account.id)}
769
- >
770
- <BankCard
771
- bankName={account.bankName}
772
- image={account.logoUrl}
773
- subText={`${account.type} | **${formatAccountNumber(account.maskedAccountNumber || "0")}`}
774
- onClick={() => toggleBankSelection(account.id)}
775
- rightSection={
861
+ />
862
+ )}
863
+ />
864
+ </CollapsibleTrigger>
865
+ <CollapsibleContent open={isOpen} className="px-4 pb-4">
866
+ {accounts.map(account => (
867
+ <div className='mt-2 flex flex-col gap-2' key={account.id}>
868
+ <OuterCard
869
+ selected={selectedAccounts.includes(account.id)}
870
+ onSelect={() => toggleBankSelection(account.id)}
871
+ >
872
+ <BankCard
873
+ bankName={account.bankName}
874
+ image={account.logoUrl}
875
+ subText={`${account.type} | **${formatAccountNumber(account.maskedAccountNumber || "0")}`}
876
+ onClick={() => toggleBankSelection(account.id)}
877
+ rightSection={
878
+ <div onClick={(e) => e.stopPropagation()}>
776
879
  <Checkbox
777
880
  id={`checkbox-${account.id}`}
778
881
  checked={selectedAccounts.includes(account.id)}
779
882
  onCheckedChange={() => toggleBankSelection(account.id)}
780
883
  />
781
- }
782
- />
783
- </OuterCard>
784
- </div>
785
- ))}
786
- </CollapsibleContent>
787
- </Collapsible>
788
- );
789
- })}
790
- </div>
884
+ </div>
885
+ }
886
+ />
887
+ </OuterCard>
888
+ </div>
889
+ ))}
890
+ </CollapsibleContent>
891
+ </Collapsible>
892
+ );
893
+ })}
791
894
  </div>
895
+ </div>
792
896
 
793
- {/* Linked Accounts Section - Only for returning users */}
794
- {isUserPresent && Object.keys(activeLinkedAccount).length > 0 && (
795
- <div className='mt-6'>
796
- <Collapsible open={tabOpen} onOpenChange={setTabOpen} className="border-none">
797
- <CollapsibleTrigger asChild>
798
- <div className='flex items-center bg-card py-4 px-5 pr-4 rounded-t-xl justify-between gap-2 cursor-pointer shadow-sm shadow-black/5'>
799
- <div className='flex flex-col justify-center relative'>
800
- <p className="text-lg text-stone-500 dark:text-gray-200 font-bold">Linked accounts</p>
801
- <p className="text-sm text-stone-700 dark:text-gray-400">
802
- {Object.keys(activeLinkedAccount).length} {Object.keys(activeLinkedAccount).length > 1 ? "linked accounts" : "linked account"}
803
- </p>
804
- </div>
805
- <div>
806
- <ChevronDownIcon
807
- size={20}
808
- className={`h-5 w-5 transition-transform duration-300 ${
809
- tabOpen ? "rotate-180" : "rotate-0"
897
+ {/* Linked Accounts Section - Only for returning users */}
898
+ {isUserPresent && Object.keys(activeLinkedAccount).length > 0 && (
899
+ <div className='mt-6'>
900
+ <Collapsible open={tabOpen} onOpenChange={setTabOpen} className="border-none">
901
+ <CollapsibleTrigger asChild>
902
+ <div className='flex items-center bg-card py-4 px-5 pr-4 rounded-t-xl justify-between gap-2 cursor-pointer shadow-sm shadow-black/5'>
903
+ <div className='flex flex-col justify-center relative'>
904
+ <p className="text-md text-stone-500 dark:text-gray-200 font-semibold ">Linked accounts</p>
905
+ <p className="text-sm text-stone-700 dark:text-gray-400">
906
+ {/* {Object.keys(activeLinkedAccount).length} {Object.keys(activeLinkedAccount).length > 1 ? "linked accounts" : "linked account"} */}
907
+ {linkedAccountCount || 0} linked account{linkedAccountCount > 1 ? "s" : ""} across {Object.keys(activeLinkedAccount).length} FI Type{Object.keys(activeLinkedAccount).length > 1 ? "s" : ""}
908
+ </p>
909
+ </div>
910
+ <div>
911
+ <ChevronDownIcon
912
+ size={18}
913
+ className={`h-5 w-5 transition-transform duration-300 ${tabOpen ? "rotate-180" : "rotate-0"
810
914
  }`}
811
- />
812
- </div>
915
+ />
813
916
  </div>
814
- </CollapsibleTrigger>
815
- <CollapsibleContent open={tabOpen} className="pb-4 bg-card rounded-b-xl">
816
- {Object.entries(activeLinkedAccount as Record<string, LinkedAccount[]>).map(([fiType, accounts], index) => {
817
- const isOpen = openLinkAcc === index;
818
- return (
819
- <Collapsible
820
- key={fiType}
821
- open={isOpen}
822
- onOpenChange={() => setOpenLinkAcc(isOpen ? null : index)}
823
- >
824
- <CollapsibleTrigger className="flex justify-between items-center p-4 w-[100%]">
825
- <SectionTitle
826
- title={fiType}
827
- rightSection={(
828
- <ChevronDownIcon
829
- size={20}
830
- className={`h-5 w-5 transition-transform duration-300 ${
831
- isOpen ? "rotate-180" : "rotate-0"
917
+ </div>
918
+ </CollapsibleTrigger>
919
+ <CollapsibleContent open={tabOpen} className="pb-4 bg-card rounded-b-xl">
920
+ {Object.entries(activeLinkedAccount as Record<string, LinkedAccount[]>).map(([f, accounts], index) => {
921
+ const isOpen = openLinkAcc === index;
922
+ const fiType = removeUnderscores(f) || f
923
+ return (
924
+ <Collapsible
925
+ key={fiType}
926
+ open={isOpen}
927
+ onOpenChange={() => setOpenLinkAcc(isOpen ? null : index)}
928
+ >
929
+ <CollapsibleTrigger className="flex justify-between items-center p-4 w-[100%]">
930
+ <SectionTitle
931
+ title={fiType}
932
+ className='text-sm md:text-sm'
933
+ rightSection={(
934
+ <ChevronDownIcon
935
+ size={18}
936
+ className={`h-5 w-5 transition-transform duration-300 ${isOpen ? "rotate-180" : "rotate-0"
832
937
  }`}
938
+ />
939
+ )}
940
+ />
941
+ </CollapsibleTrigger>
942
+ <CollapsibleContent open={isOpen} className="px-4 pb-4">
943
+ {accounts.map(account => (
944
+ <div className='mt-2 flex flex-col gap-2' key={account.id}>
945
+ <OuterCard
946
+ selected={true}
947
+ // onSelect={() => toggleBankSelection(account.accountRefNumber)}
948
+ >
949
+ <BankCard
950
+ bankName={account.bankName}
951
+ image={account.logoUrl}
952
+ subText={`${account.type} | **${formatAccountNumber(account.maskedAccountNumber || "0")}`}
953
+ // onClick={() => toggleBankSelection(account.accountRefNumber)}
954
+ // rightSection={
955
+ // <Checkbox
956
+ // id={`checkbox-${account.id}`}
957
+ // checked={selectedAccounts.includes(account.accountRefNumber)}
958
+ // onCheckedChange={() => toggleBankSelection(account.accountRefNumber)}
959
+ // />
960
+ // }
833
961
  />
834
- )}
835
- />
836
- </CollapsibleTrigger>
837
- <CollapsibleContent open={isOpen} className="px-4 pb-4">
838
- {accounts.map(account => (
839
- <div className='mt-2 flex flex-col gap-2' key={account.id}>
840
- <OuterCard
841
- selected={true}
842
- // onSelect={() => toggleBankSelection(account.accountRefNumber)}
843
- >
844
- <BankCard
845
- bankName={account.bankName}
846
- image={account.logoUrl}
847
- subText={`${account.type} | **${formatAccountNumber(account.maskedAccountNumber || "0")}`}
848
- // onClick={() => toggleBankSelection(account.accountRefNumber)}
849
- // rightSection={
850
- // <Checkbox
851
- // id={`checkbox-${account.id}`}
852
- // checked={selectedAccounts.includes(account.accountRefNumber)}
853
- // onCheckedChange={() => toggleBankSelection(account.accountRefNumber)}
854
- // />
855
- // }
856
- />
857
- </OuterCard>
858
- </div>
859
- ))}
860
- </CollapsibleContent>
861
- </Collapsible>
862
- );
863
- })}
864
- </CollapsibleContent>
865
- </Collapsible>
866
- </div>
867
- )}
868
- </>
869
- ) : (
962
+ </OuterCard>
963
+ </div>
964
+ ))}
965
+ </CollapsibleContent>
966
+ </Collapsible>
967
+ );
968
+ })}
969
+ </CollapsibleContent>
970
+ </Collapsible>
971
+ </div>
972
+ )}
973
+ </>
974
+ ) : (
870
975
  renderAccountGroups()
871
976
  )}
872
977
  </div>
873
- {(requiredCategory?.every(fiType => totalCategory?.includes(fiType)) || state?.isAutoDiscovery || !shouldShowDiscoverMore) ? null :
978
+ {(requiredCategory?.every(fiType => totalCategory?.includes(fiType)) || state?.isAutoDiscovery || !shouldShowDiscoverMore) ? null :
874
979
  <Button
875
980
  variant={'outline'}
876
981
  className='bg-primary-foreground text-primary border-primary hover:bg-primary/10 h-[50px] dark:bg-ring'