saafe-redirection-flow 2.0.0 → 2.1.0

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.
@@ -8,13 +8,23 @@ import { feedbackService } from "@/services/api";
8
8
  import { StarFilledIcon } from "@radix-ui/react-icons";
9
9
  import { useNavigationBlock } from "@/store/NavigationBlockContext";
10
10
  import { MobileAppDownload } from "@/components/mobileAppDownload";
11
+ import { useLocation } from "react-router-dom";
12
+ import AlertComp from "@/components/alert/alert";
13
+ import { X } from "lucide-react";
14
+ import { useFipStore } from "@/store/fip.store";
15
+
16
+ import { HelpModal } from "@/components/modal/HelpModal";
11
17
 
12
18
  const Success = () => {
19
+ const location = useLocation();
20
+ const consentResult: any[] = location.state?.consentResult;
21
+ const consents: any[] = location.state?.consents;
13
22
  const [rating, setRating] = useState<number | null>(null);
14
23
  const [hoveredRating, setHoveredRating] = useState<number | null>(null);
15
24
  const [isSubmitted, setIsSubmitted] = useState(false);
16
25
  const [isSubmitting, setIsSubmitting] = useState(false);
17
26
  const [countdown, setCountdown] = useState(10);
27
+ const { selectedAccountToLink } = useFipStore()
18
28
  const { decodedInfo } = useRedirectStore();
19
29
  const { allowNextNavigation } = useNavigationBlock();
20
30
  const consentHandle = decodedInfo?.srcref || '';
@@ -24,9 +34,9 @@ const Success = () => {
24
34
  if (countdown === 1) {
25
35
  console.log("Sending message to parent -->> From AA");
26
36
  window.parent.postMessage(
27
- {
28
- type: 'AA',
29
- status: 'approved',
37
+ {
38
+ type: 'AA',
39
+ status: 'approved',
30
40
  flowCompleted: true,
31
41
  consentHandle: consentHandle,
32
42
  userRating: rating,
@@ -94,8 +104,9 @@ const Success = () => {
94
104
  };
95
105
 
96
106
  return (
97
- <div className="flex flex-col min-h-screen h-full">
107
+ <div className="flex flex-col h-full">
98
108
  <MobileBackground
109
+ className="h-full min-h-screen"
99
110
  blobConfig={{
100
111
  count: 8,
101
112
  color: "rgba(255, 255, 255, 0.4)",
@@ -104,104 +115,142 @@ const Success = () => {
104
115
  speed: 4,
105
116
  }}
106
117
  >
107
- <div className="p-6 flex flex-col min-h-screen">
108
- {/* Redirecting message */}
109
- {decodedInfo?.redirect && (
110
- <div className="absolute top-4 sm:top-6 left-0 right-0 text-center">
111
- <p className="text-white/80 text-sm">
112
- Redirecting in <span className="font-semibold">00:{countdown < 10 ? `0${countdown}` : countdown}</span>
113
- </p>
114
- </div>
115
- )}
118
+ <div className="flex flex-col h-full min-h-screen p-4 sm:p-6">
119
+ {/* Header with help and timer */}
120
+ <div className="flex justify-between items-start mb-4 sm:mb-6">
121
+ <HelpModal
122
+ variant="light"
123
+ iconSize={16}
124
+ textSize="text-xs sm:text-sm"
125
+ buttonClassName="self-start"
126
+ />
127
+ {decodedInfo?.redirect && (
128
+ <div className="text-center">
129
+ <p className="text-white/80 text-xs sm:text-sm">
130
+ Redirecting in <span className="font-semibold">00:{countdown < 10 ? `0${countdown}` : countdown}</span>
131
+ </p>
132
+ </div>
133
+ )}
134
+ </div>
116
135
 
117
136
  {/* Main content - centered vertically and horizontally */}
118
137
  <div className="flex-1 flex flex-col justify-center items-center pt-10 sm:pt-16 pb-10 sm:pb-20">
119
- <motion.div
120
- initial={{ y: 20, opacity: 0 }}
121
- animate={{ y: 0, opacity: 1 }}
122
- transition={{ delay: 0.2, duration: 0.5 }}
138
+ <motion.div
139
+ initial={{ y: 20, opacity: 0 }}
140
+ animate={{ y: 0, opacity: 1 }}
141
+ transition={{ delay: 0.2, duration: 0.5 }}
123
142
  className="text-white flex flex-col items-center w-full max-w-md mx-auto px-4"
124
- >
143
+ >
125
144
  <div className="rounded-full mb-10 sm:mb-16">
126
- <motion.img
127
- src={checkIcon}
128
- alt="Check Icon"
145
+ <motion.img
146
+ src={checkIcon}
147
+ alt="Check Icon"
129
148
  className="w-32 h-32 sm:w-40 sm:h-40 md:w-48 md:h-48"
130
- initial={{ scale: 0, rotate: -10 }}
131
- animate={{ scale: 1, rotate: 0 }}
132
- transition={{
133
- type: "spring",
134
- damping: 8,
135
- stiffness: 100,
136
- delay: 0.9,
137
- duration: 4
138
- }}
139
- />
140
- </div>
149
+ initial={{ scale: 0, rotate: -10 }}
150
+ animate={{ scale: 1, rotate: 0 }}
151
+ transition={{
152
+ type: "spring",
153
+ damping: 8,
154
+ stiffness: 100,
155
+ delay: 0.9,
156
+ duration: 4
157
+ }}
158
+ />
159
+ </div>
141
160
 
142
- <motion.h1
161
+ <motion.h1
143
162
  className="text-xl md:text-3xl font-semibold mb-5 sm:mb-6 text-center"
144
- initial={{ opacity: 0 }}
145
- animate={{ opacity: 1 }}
146
- transition={{ delay: 1.5 }}
147
- >
148
- Approved Successfully
149
- </motion.h1>
150
-
151
- <motion.p
152
- className="text-white/80 text-center mb-10 sm:mb-14 text-sm sm:text-base md:text-lg max-w-sm"
153
- initial={{ opacity: 0 }}
154
- animate={{ opacity: 1 }}
155
- transition={{ delay: 1.5 }}
156
- >
157
- You can pause/revoke this consent anytime using Saafe app
158
- </motion.p>
163
+ initial={{ opacity: 0 }}
164
+ animate={{ opacity: 1 }}
165
+ transition={{ delay: 1.5 }}
166
+ >
167
+ Approved Successfully
168
+ </motion.h1>
169
+
170
+ <motion.p
171
+ className="text-white/80 text-center mb-10 sm:mb-8 text-sm sm:text-base md:text-lg max-w-sm"
172
+ initial={{ opacity: 0 }}
173
+ animate={{ opacity: 1 }}
174
+ transition={{ delay: 1.5 }}
175
+ >
176
+ You can pause/revoke this consent anytime using Saafe app
177
+ </motion.p>
159
178
 
179
+ {consentResult?.filter(item => item.ConsentStatus === 'FAILED')?.length > 0 && (
180
+ <AlertComp
181
+ type="error"
182
+ icon={
183
+ <div className='flex bg-red-600 p-2 rounded-full'>
184
+ <X
185
+ strokeWidth='2.3'
186
+ className='h-[18px] w-[18px] text-white'
187
+ />
188
+ </div>
189
+ }
190
+ title={`Out of ${consents?.length} consents, ${consentResult?.filter(item => item.ConsentStatus === 'FAILED')?.length} was rejected`}
191
+ description={
192
+ <div className="text-left mt-1">
193
+ {
194
+ (() => {
195
+ const fipCodes = consentResult?.filter(item => item.ConsentStatus === 'FAILED')?.map(item =>
196
+ item.reasonForRejection.match(/\[(.*?)\]/)?.[1]
197
+ );
198
+ const fipNames = selectedAccountToLink?.filter(item => fipCodes?.join(", ").split(", ").includes(item.fipHandle))?.map(item => item.fipName)
199
+ // remove the duplicate fipCodes
200
+ const uniqueFipCodes = [...new Set((fipNames))];
201
+ const result = `Consent rejected by FIP: ${uniqueFipCodes?.join(", ")}`;
202
+ return <div>{result}</div>;
203
+ })()
204
+ }
205
+ </div>
206
+ }
207
+ />
208
+ )}
160
209
  {/* Mobile App Download - animated to appear last */}
161
210
  <motion.div
162
- className="w-full max-w-xs mx-auto"
211
+ className="w-full max-w-xs mx-auto mt-5"
163
212
  initial={{ opacity: 0, y: 20 }}
164
213
  animate={{ opacity: 1, y: 0 }}
165
214
  transition={{ delay: 2.5, duration: 0.7 }}
166
215
  >
167
- <MobileAppDownload />
216
+ <MobileAppDownload />
168
217
  </motion.div>
169
-
170
- {/* Rating Component */}
171
- <motion.div
218
+
219
+ {/* Rating Component */}
220
+ <motion.div
172
221
  className="mt-12 sm:mt-16 w-full max-w-xs mx-auto px-4 text-center"
173
- initial={{ opacity: 0, y: 20 }}
174
- animate={{ opacity: 1, y: 0 }}
175
- transition={{ delay: 2.0 }}
176
- >
222
+ initial={{ opacity: 0, y: 20 }}
223
+ animate={{ opacity: 1, y: 0 }}
224
+ transition={{ delay: 2.0 }}
225
+ >
177
226
  <h3 className="text-white text-sm sm:text-base md:text-lg mb-4 sm:mb-6">Rate your experience</h3>
178
227
  <div className="flex justify-center gap-3 sm:gap-5">
179
- {[1, 2, 3, 4, 5].map((star) => (
180
- <button
181
- key={star}
182
- onClick={() => handleRatingClick(star)}
183
- onMouseEnter={() => setHoveredRating(star)}
184
- onMouseLeave={() => setHoveredRating(null)}
228
+ {[1, 2, 3, 4, 5].map((star) => (
229
+ <button
230
+ key={star}
231
+ onClick={() => handleRatingClick(star)}
232
+ onMouseEnter={() => setHoveredRating(star)}
233
+ onMouseLeave={() => setHoveredRating(null)}
185
234
  className="focus:outline-none transition-transform hover:scale-110 p-1 sm:p-2"
186
- disabled={isSubmitting}
187
- >
188
- <StarFilledIcon
189
- className={`
235
+ disabled={isSubmitting}
236
+ >
237
+ <StarFilledIcon
238
+ className={`
190
239
  transition-colors duration-200 w-6 h-6 sm:w-8 sm:h-8 md:w-9 md:h-9
191
240
  ${(hoveredRating !== null ? star <= hoveredRating : star <= (rating || 0))
192
- ? "fill-white text-white"
193
- : "text-white/40 fill-transparent"}
241
+ ? "fill-white text-white"
242
+ : "text-white/40 fill-transparent"}
194
243
  ${isSubmitting ? "opacity-70" : ""}
195
244
  `}
196
- />
197
- </button>
198
- ))}
199
- </div>
200
- {isSubmitted && (
245
+ />
246
+ </button>
247
+ ))}
248
+ </div>
249
+ {isSubmitted && (
201
250
  <p className="mt-3 text-white/80 text-xs sm:text-sm">Thank you for your feedback!</p>
202
- )}
251
+ )}
252
+ </motion.div>
203
253
  </motion.div>
204
- </motion.div>
205
254
  </div>
206
255
 
207
256
  {/* Footer */}
@@ -8,6 +8,48 @@ import { fiTypeCategoryMap } from '@/const/fiTypeCategoryMap'
8
8
  export interface Identifier {
9
9
  category: string
10
10
  type: string
11
+ value?: string
12
+ }
13
+
14
+ export interface DiscoveredAccount {
15
+ FIType: string
16
+ accType: string
17
+ accRefNumber: string
18
+ maskedAccNumber: string
19
+ state: string | null
20
+ amcName: string | null
21
+ logoUrl: string | null
22
+ }
23
+
24
+ export interface AccountToLink {
25
+ id: string
26
+ type: string
27
+ maskedAccountNumber: string
28
+ bankName: string
29
+ logoUrl?: string
30
+ isNew?: boolean
31
+ fipId: string
32
+ fipName: string
33
+ signature: string
34
+ DiscoveredAccounts?: DiscoveredAccount[]
35
+ }
36
+
37
+ export interface AccountStatus {
38
+ id: string
39
+ fipId: string
40
+ status: string
41
+ accounts?: string
42
+ otp?: string
43
+ }
44
+
45
+ export interface AccountForConsent {
46
+ linkRefNumber: string
47
+ fiType: string
48
+ bankName: string
49
+ accountType: string
50
+ maskedAccNumber: string
51
+ logoUrl?: string
52
+ fipHandle: string
11
53
  }
12
54
 
13
55
  export interface FipData {
@@ -32,14 +74,16 @@ export const MAIN_STEPS = {
32
74
  LOGIN: 'login',
33
75
  LINK_ACCOUNTS: 'link_accounts',
34
76
  REVIEW_CONSENT: 'review_consent'
35
- }
77
+ } as const
36
78
 
37
79
  // Define flow steps for each category
38
80
  export const CATEGORY_STEPS = {
39
81
  LIST: 'list',
40
82
  DISCOVERY: 'discovery',
41
83
  LINKING: 'linking'
42
- }
84
+ } as const
85
+
86
+ type CategoryStepType = typeof CATEGORY_STEPS[keyof typeof CATEGORY_STEPS]
43
87
 
44
88
  export interface FipState {
45
89
  fips: FipData[]
@@ -49,16 +93,15 @@ export interface FipState {
49
93
  activeCategory: string | null
50
94
  categories: string[]
51
95
  completedCategories: string[]
52
- currentCategoryStep: CATEGORY_STEPS
96
+ currentCategoryStep: CategoryStepType
53
97
  isLoading: boolean
54
98
  error: string | null
55
- selectedAccountToLink: any[]
56
- signature: string | null
99
+ selectedAccountToLink: AccountToLink[]
57
100
  originalAccounts: AutoDiscoveryResponse['Accounts']
58
- accountsStatusArray: any[]
59
- identifiers: any[]
60
- accountForConsent: any[]
61
- accountsToNotify: any[]
101
+ accountsStatusArray: AccountStatus[]
102
+ identifiers: Identifier[]
103
+ accountForConsent: AccountForConsent[]
104
+ accountsToNotify: string[]
62
105
 
63
106
  // Navigation state
64
107
  currentStep: string
@@ -76,21 +119,20 @@ export interface FipState {
76
119
  removeSelectedFip: (category: string, fipId: string) => void
77
120
  setSelectedMobileNumber: (mobileNumber: string) => void
78
121
  setActiveCategory: (category: string | null) => void
79
- setCategoryStep: (step: string) => void
122
+ setCategoryStep: (step: CategoryStepType) => void
80
123
  completeCategory: (category: string) => void
81
124
  setCurrentStep: (stepId: string) => void
82
125
  setCurrentChildStep: (childStepId: string | null) => void
83
126
  clearSelections: () => void
84
127
  setIsLoading: (isLoading: boolean) => void
85
128
  setError: (error: string | null) => void
86
- setSignature: (signature: string | null) => void
87
- setSelectedAccountToLink: (account: any[]) => void
129
+ setSelectedAccountToLink: (account: AccountToLink[]) => void
88
130
  setOriginalAccounts: (
89
131
  originalAccounts: AutoDiscoveryResponse['Accounts']
90
132
  ) => void
91
- setAccountsStatusArray: (data: any[]) => void
92
- setIdentifiers: (identifiersList: any[]) => void
93
- setAccountForConsent: (accountForConsent: any[]) => void
133
+ setAccountsStatusArray: (data: AccountStatus[]) => void
134
+ setIdentifiers: (identifiersList: Identifier[]) => void
135
+ setAccountForConsent: (accountForConsent: AccountForConsent[]) => void
94
136
  setAccountsToNotify: (data: string) => void
95
137
  }
96
138
 
@@ -119,7 +161,7 @@ const initialNavigationSteps: StepItem[] = [
119
161
 
120
162
  // Helper function to extract categories
121
163
  export const extractCategories = (groupedFips: string[]): string[] => {
122
- const category = new Set()
164
+ const category = new Set<string>()
123
165
  console.log('groupedFips', groupedFips);
124
166
 
125
167
  groupedFips.forEach((i) => {
@@ -131,13 +173,13 @@ export const extractCategories = (groupedFips: string[]): string[] => {
131
173
  })
132
174
  })
133
175
 
134
- const result = [...category]
176
+ const result = Array.from(category)
135
177
  if (result.length > 1) {
136
178
  const order = Object.keys(fiTypeCategoryMap)
137
- return result.sort((a, b) => order.indexOf(a) - order.indexOf(b)) as unknown as string[]
179
+ return result.sort((a, b) => order.indexOf(a) - order.indexOf(b))
138
180
  }
139
181
 
140
- return result as unknown as string[]
182
+ return result
141
183
  }
142
184
 
143
185
  // Helper function to create child steps from categories
@@ -179,6 +221,7 @@ export const useFipStore = create<FipState>()(
179
221
  fips: [],
180
222
  groupedFips: {},
181
223
  selectedFips: {},
224
+ autoDiscoveryFips: {},
182
225
  selectedMobileNumber: undefined,
183
226
  activeCategory: null,
184
227
  categories: [],
@@ -187,7 +230,6 @@ export const useFipStore = create<FipState>()(
187
230
  isLoading: false,
188
231
  error: null,
189
232
  selectedAccountToLink: [],
190
- signature: null,
191
233
  originalAccounts: [],
192
234
  accountsStatusArray: [],
193
235
  identifiers: [],
@@ -207,10 +249,16 @@ export const useFipStore = create<FipState>()(
207
249
 
208
250
  // Initialize selectedFips with empty arrays for each category
209
251
  const selectedFips: { [category: string]: string[] } = {}
252
+ // const autoDiscoveryFips: { [category: string]: string[] } = {}
210
253
  categories.forEach(category => {
211
254
  selectedFips[category] = []
255
+ // autoDiscoveryFips[category] = []
256
+ // const fipsList = fips[category]
257
+ // autoDiscoveryFips[category] = fipsList.filter(fip => fip.isEnabled && fip.isOnBoarded)
212
258
  })
213
259
 
260
+
261
+
214
262
  // Create child steps from categories
215
263
  const childSteps = createChildSteps(categories)
216
264
  const currentChildStep = categories.length > 0 ? categories[0] : null
@@ -245,8 +293,6 @@ export const useFipStore = create<FipState>()(
245
293
  setSelectedAccountToLink: accountIds =>
246
294
  set({ selectedAccountToLink: accountIds }),
247
295
 
248
- setSignature: signature => set({ signature }),
249
-
250
296
  setIdentifiers: identifiers => set({ identifiers }),
251
297
 
252
298
  setOriginalAccounts: originalAccounts => set({ originalAccounts }),
@@ -363,12 +409,13 @@ export const useFipStore = create<FipState>()(
363
409
 
364
410
  setIsLoading: isLoading => set({ isLoading }),
365
411
 
366
- setAccountsToNotify: (data: string | null) => {
412
+ setAccountsToNotify: (data: string) => {
367
413
  console.log('data', data);
368
-
369
- set(state => ({
370
- accountsToNotify: [...state?.accountsToNotify, data]
371
- }))
414
+ if (data) {
415
+ set(state => ({
416
+ accountsToNotify: [...(state.accountsToNotify || []), data]
417
+ }))
418
+ }
372
419
  },
373
420
 
374
421
  setError: error => set({ error })
@@ -388,7 +435,6 @@ export const useFipStore = create<FipState>()(
388
435
  selectedAccountToLink: state.selectedAccountToLink,
389
436
  originalAccounts: state.originalAccounts,
390
437
  identifiers: state.identifiers,
391
- signature: state.signature,
392
438
  accountForConsent: state.accountForConsent
393
439
  })
394
440
  }
@@ -13,6 +13,7 @@ export interface RedirectState {
13
13
  setDecodingError: (error: string) => void;
14
14
  clearDecodedInfo: () => void;
15
15
  getCustomStyle: () => Record<string, string> | null;
16
+ updateIdentifiers: (pan?: string, dob?: string) => void;
16
17
  }
17
18
 
18
19
  export const useRedirectStore = create<RedirectState>()(
@@ -60,6 +61,18 @@ export const useRedirectStore = create<RedirectState>()(
60
61
  return null;
61
62
  }
62
63
  },
64
+
65
+ updateIdentifiers: (pan?: string, dob?: string) => {
66
+ const { decodedInfo } = get();
67
+ if (decodedInfo) {
68
+ const updatedInfo = {
69
+ ...decodedInfo,
70
+ pan: pan || decodedInfo.pan,
71
+ dob: dob || decodedInfo.dob,
72
+ };
73
+ set({ decodedInfo: updatedInfo });
74
+ }
75
+ },
63
76
  }),
64
77
  {
65
78
  name: "saafe-redirect-storage",
@@ -0,0 +1,126 @@
1
+ export interface AutoDiscoveryConfig {
2
+ isEnabled: boolean;
3
+ showDiscoverMore: boolean;
4
+ }
5
+
6
+ export interface AutoDiscoveryConfigs {
7
+ INSURANCE: AutoDiscoveryConfig;
8
+ GST: AutoDiscoveryConfig;
9
+ INVESTMENTS: AutoDiscoveryConfig;
10
+ }
11
+
12
+ /**
13
+ * Parse the auto discovery configuration from the decoded info
14
+ */
15
+ export const parseAutoDiscoveryConfig = (otherAppCustomization?: string): AutoDiscoveryConfigs => {
16
+ const defaultConfig: AutoDiscoveryConfigs = {
17
+ INSURANCE: { isEnabled: false, showDiscoverMore: true },
18
+ GST: { isEnabled: false, showDiscoverMore: true },
19
+ INVESTMENTS: { isEnabled: false, showDiscoverMore: true }
20
+ };
21
+
22
+ if (!otherAppCustomization) {
23
+ return defaultConfig;
24
+ }
25
+
26
+ try {
27
+ const config = JSON.parse(otherAppCustomization);
28
+
29
+ return {
30
+ INSURANCE: {
31
+ isEnabled: config.INSURANCE_AutoDiscovery === 'true',
32
+ showDiscoverMore: config.INSURANCE_ShowDiscoverMore === undefined ? true : config.INSURANCE_ShowDiscoverMore === 'true'
33
+ },
34
+ GST: {
35
+ isEnabled: config.GST_AutoDiscovery === 'true',
36
+ showDiscoverMore: config.GST_ShowDiscoverMore === undefined ? true : config.GST_ShowDiscoverMore === 'true'
37
+ },
38
+ INVESTMENTS: {
39
+ isEnabled: config.INVESTMENTS_AutoDiscovery === 'true',
40
+ showDiscoverMore: config.INVESTMENTS_ShowDiscoverMore === undefined ? true : config.INVESTMENTS_ShowDiscoverMore === 'true'
41
+ }
42
+ };
43
+ } catch (error) {
44
+ console.error('Failed to parse auto discovery configuration:', error);
45
+ return defaultConfig;
46
+ }
47
+ };
48
+
49
+ /**
50
+ * Check if auto discovery is enabled for a specific category
51
+ */
52
+ export const isAutoDiscoveryEnabled = (category: string, otherAppCustomization?: string): boolean => {
53
+ const config = parseAutoDiscoveryConfig(otherAppCustomization);
54
+ const categoryKey = category.toUpperCase() as keyof AutoDiscoveryConfigs;
55
+
56
+ // Only enable auto discovery for supported categories
57
+ if (!['INSURANCE', 'GST', 'INVESTMENTS'].includes(categoryKey)) {
58
+ return false;
59
+ }
60
+
61
+ return config[categoryKey]?.isEnabled || false;
62
+ };
63
+
64
+ /**
65
+ * Get FIP IDs for auto discovery
66
+ * If FIP IDs are configured in decodedInfo, use those
67
+ * Otherwise, use all FIPs for the category
68
+ */
69
+ export const getAutoDiscoveryFipIds = (
70
+ category: string,
71
+ configuredFipIds?: string,
72
+ groupedFips?: Record<string, any[]>
73
+ ): string[] => {
74
+ // If FIP IDs are configured and not empty, use them
75
+ if (configuredFipIds && configuredFipIds.trim()) {
76
+ return configuredFipIds.split(',').map(fipId => fipId.trim()).filter(fipId => fipId.length > 0);
77
+ }
78
+
79
+ // Otherwise, get all FIPs for the category from the store
80
+ if (groupedFips) {
81
+ // Import fiTypeCategoryMap to get the correct FI types for the category
82
+ const fiTypeCategoryMap: Record<string, string[]> = {
83
+ BANK: ['DEPOSIT', 'RECURRING_DEPOSIT', 'TERM_DEPOSIT', 'BANK'],
84
+ INVESTMENTS: [
85
+ 'EQUITIES',
86
+ 'MUTUAL_FUNDS',
87
+ 'OTHER_INVESTMENTS',
88
+ 'IDR',
89
+ 'REIT',
90
+ 'CIS',
91
+ 'INVIT',
92
+ 'AIF',
93
+ 'ETF',
94
+ 'SIP',
95
+ 'NPS (Retirement focused)',
96
+ 'PENSION'
97
+ ],
98
+ INSURANCE: ['INSURANCE_POLICIES', 'LIFE_INSURANCE', 'GENERAL_INSURANCE', 'INSURANCE'],
99
+ GST: ['GSTR1_3B', 'GST']
100
+ };
101
+
102
+ const categoryUpper = category.toUpperCase();
103
+ const fiTypesForCategory = fiTypeCategoryMap[categoryUpper] || [];
104
+
105
+ // Get all FIPs that match the FI types for this category
106
+ const categoryFips = fiTypesForCategory
107
+ .flatMap(fiType => groupedFips[fiType] || [])
108
+ .reduce((unique: any[], fip: any) => {
109
+ // Remove duplicates based on ID
110
+ if (!unique.find((existingFip: any) => existingFip.id === fip.id)) {
111
+ unique.push(fip);
112
+ }
113
+ return unique;
114
+ }, [])
115
+ // IMPORTANT: Filter to only include enabled and onboarded FIPs
116
+ .filter((fip: any) => fip.isEnabled === true && fip.isOnBoarded === true);
117
+
118
+ console.log(`Auto Discovery: Found ${categoryFips.length} enabled & onboarded FIPs for category ${categoryUpper}:`,
119
+ categoryFips.map((fip: any) => ({ id: fip.id, name: fip.name, isEnabled: fip.isEnabled, isOnBoarded: fip.isOnBoarded }))
120
+ );
121
+
122
+ return categoryFips.map((fip: any) => fip.id);
123
+ }
124
+
125
+ return [];
126
+ };
Binary file