saafe-redirection-flow 2.0.0 → 2.2.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/aa-redirection-1607251826.zip +0 -0
  3. package/docs/features/account-discovery.md +803 -0
  4. package/docs/features/authentication.md +583 -0
  5. package/docs/features/consent-management.md +740 -0
  6. package/docs/features/index.md +206 -0
  7. package/docs/features/navigation-routing.md +846 -0
  8. package/docs/features/overview.md +554 -0
  9. package/docs/features/theming-internationalization.md +982 -0
  10. package/docs/index.md +1 -1
  11. package/package.json +1 -1
  12. package/src/components/AutoDiscoveryLoader.tsx +29 -0
  13. package/src/components/LinkedAccountTypeAccordion.tsx +154 -0
  14. package/src/components/alert/alert.tsx +10 -4
  15. package/src/components/modal/HelpModal.tsx +122 -0
  16. package/src/components/session/SessionTimer.tsx +20 -33
  17. package/src/components/title/AppBar.tsx +1 -1
  18. package/src/components/ui/bottom-sheet.tsx +1 -1
  19. package/src/components/ui/frosted-panel.tsx +0 -2
  20. package/src/components/ui/otp-input.tsx +38 -6
  21. package/src/hooks/use-account-discovery.ts +13 -6
  22. package/src/hooks/use-auto-discovery.ts +226 -0
  23. package/src/index.css +3 -0
  24. package/src/pages/accounts/AccountsToProceed.tsx +598 -59
  25. package/src/pages/accounts/Discover.tsx +127 -9
  26. package/src/pages/accounts/DiscoverAccount.tsx +195 -42
  27. package/src/pages/accounts/LinkSelectedAccounts.tsx +15 -9
  28. package/src/pages/accounts/OldUser.tsx +184 -79
  29. package/src/pages/accounts/link-accounts.tsx +19 -11
  30. package/src/pages/consent/ReviewConsent.tsx +19 -10
  31. package/src/pages/consent/rejected.tsx +17 -8
  32. package/src/pages/consent/success.tsx +125 -76
  33. package/src/services/api/account.service.ts +2 -2
  34. package/src/store/fip.store.ts +74 -28
  35. package/src/store/redirect.store.ts +19 -0
  36. package/src/utils/auto-discovery.ts +126 -0
  37. package/src/utils/toast-helpers.ts +5 -0
  38. package/stage-aa-2506251021.zip +0 -0
@@ -0,0 +1,226 @@
1
+ import { useMutation } from '@tanstack/react-query'
2
+ import { useState } from 'react'
3
+ import { useFipStore, DiscoveredAccount } from '@/store/fip.store'
4
+ import { useRedirectStore } from '@/store/redirect.store'
5
+ import { useAuthStore } from '@/store/auth.store'
6
+ import { fiTypeCategoryMap } from '@/const/fiTypeCategoryMap'
7
+ import { accountService } from '@/services/api/account.service'
8
+ import { ProcessedDiscoveryResult } from './use-account-discovery'
9
+ import { getAutoDiscoveryFipIds } from '@/utils/auto-discovery'
10
+
11
+ /**
12
+ * Hook for auto discovery - discovers accounts from all available FIPs for a category
13
+ */
14
+ export function useAutoDiscovery() {
15
+ const { user } = useAuthStore()
16
+ const { groupedFips } = useFipStore()
17
+ const { decodedInfo } = useRedirectStore()
18
+ const [errorMessage, setErrorMessage] = useState<string | null>(null)
19
+ const [isLoading, setIsLoading] = useState(false)
20
+
21
+ const autoDiscoveryMutation = useMutation({
22
+ mutationFn: async (category: string): Promise<ProcessedDiscoveryResult> => {
23
+ if (!user?.phoneNumber) {
24
+ const error = new Error('Mobile number is not available')
25
+ setErrorMessage(error.message)
26
+ throw error
27
+ }
28
+
29
+ if (!decodedInfo?.fiuId) {
30
+ const error = new Error('FIU ID is not available')
31
+ setErrorMessage(error.message)
32
+ throw error
33
+ }
34
+
35
+ try {
36
+ setIsLoading(true)
37
+ const fiuId = decodedInfo.fiuId
38
+ const fiTypes = Array.isArray(decodedInfo.fiTypesRequiredForConsent)
39
+ ? decodedInfo.fiTypesRequiredForConsent.filter(i =>
40
+ fiTypeCategoryMap[category.toUpperCase()]?.includes(i)
41
+ )
42
+ : []
43
+
44
+ if (fiTypes.length === 0) {
45
+ // Fallback to category map if no FI types are provided
46
+ const categoryTypes = fiTypeCategoryMap[category.toUpperCase()]
47
+ if (categoryTypes) {
48
+ fiTypes.push(...categoryTypes)
49
+ }
50
+ }
51
+
52
+ // Get FIP IDs for auto discovery
53
+ const fipIds = getAutoDiscoveryFipIds(
54
+ category,
55
+ decodedInfo.fipId,
56
+ groupedFips
57
+ )
58
+
59
+ if (fipIds.length === 0) {
60
+ throw new Error('No FIPs available for auto discovery')
61
+ }
62
+
63
+ // Construct identifiers from redirect store data and FIP requirements
64
+ const constructIdentifiers = (fipIds: string[]) => {
65
+ const { decodedInfo } = useRedirectStore.getState()
66
+ const { fips } = useFipStore.getState()
67
+
68
+ return fipIds
69
+ .flatMap((fipId) => {
70
+ const fip = fips.find((f) => f.id === fipId)
71
+ return fip?.Identifiers || []
72
+ })
73
+ .reduce<{ type: string; value: string | null; categoryType: string }[]>(
74
+ (acc, identifier) => {
75
+ if (!acc.find((i) => i.type === identifier.type)) {
76
+ let value: string | null = null
77
+ if (identifier.type === 'PAN') {
78
+ value = decodedInfo?.pan || null
79
+ } else if (identifier.type === 'MOBILE') {
80
+ value = decodedInfo?.phoneNumber || null
81
+ } else if (identifier.type === 'DOB') {
82
+ value = decodedInfo?.dob || null
83
+ } else if (identifier.type === 'AADHAAR') {
84
+ value = null // AADHAAR not available in decodedInfo
85
+ }
86
+ acc.push({
87
+ type: identifier.type,
88
+ value,
89
+ categoryType: identifier.category
90
+ })
91
+ }
92
+ return acc
93
+ },
94
+ []
95
+ )
96
+ }
97
+
98
+ const commonIdentifiers = constructIdentifiers(fipIds).filter(id => id.value !== null) as { type: string; value: string; categoryType: string; }[]
99
+
100
+ // Log the constructed identifiers for debugging
101
+ console.log('Auto Discovery: Constructed identifiers:', commonIdentifiers)
102
+
103
+ // Perform account discovery for all FIPs
104
+ const accountPromises = fipIds.map(async (fipId) => {
105
+ return accountService.accountDiscovery({
106
+ Identifiers: [...commonIdentifiers],
107
+ FiuId: fiuId,
108
+ FipId: fipId,
109
+ FITypes: fiTypes
110
+ })
111
+ })
112
+
113
+ // Wait for all account discovery calls to complete
114
+ const results = await Promise.allSettled(accountPromises)
115
+
116
+ // Process results
117
+ const successfulResults: { fipId: string; result: { DiscoveredAccounts: DiscoveredAccount[]; signature: string } }[] = []
118
+ const failedFips: string[] = []
119
+ const errorMessages: string[] = []
120
+
121
+ results.forEach((settledResult, index) => {
122
+ const fipId = fipIds[index]
123
+ if (settledResult.status === 'fulfilled') {
124
+ successfulResults.push({ fipId, result: settledResult.value })
125
+ } else {
126
+ failedFips.push(fipId)
127
+ const error = settledResult.reason as Error & {
128
+ response?: {
129
+ status?: number;
130
+ data?: { message?: string }
131
+ }
132
+ }
133
+ if (error?.response?.data?.message) {
134
+ errorMessages.push(`${fipId}: ${error.response.data.message}`)
135
+ } else {
136
+ errorMessages.push(`${fipId}: Failed to discover accounts`)
137
+ }
138
+ }
139
+ })
140
+
141
+ // If no successful results, throw an error
142
+ if (successfulResults.length === 0) {
143
+ const combinedError = errorMessages.join('; ')
144
+ setErrorMessage(combinedError)
145
+ throw new Error(combinedError)
146
+ }
147
+
148
+ // Process and merge the accounts from successful FIPs
149
+ const originalAccounts = successfulResults.map(({ fipId, result }) => ({
150
+ fipId,
151
+ fipName: fipId, // We'll get the actual name from the FIP data
152
+ DiscoveredAccounts: result.DiscoveredAccounts,
153
+ signature: result.signature
154
+ }))
155
+
156
+ // Process the accounts from successful API responses
157
+ const processedAccounts = originalAccounts.flatMap(fipData =>
158
+ fipData.DiscoveredAccounts.map((account: DiscoveredAccount) => ({
159
+ id: account.accRefNumber,
160
+ type: account.FIType,
161
+ maskedAccountNumber: account.maskedAccNumber,
162
+ bankName: fipData.fipName,
163
+ logoUrl: account.logoUrl,
164
+ isNew: false,
165
+ fipId: fipData.fipId
166
+ }))
167
+ )
168
+
169
+ // Group accounts by type
170
+ const groupedAccounts = processedAccounts.reduce((acc: ProcessedDiscoveryResult['groupedAccounts'], account) => {
171
+ const key = account.type
172
+ if (!acc[key]) {
173
+ acc[key] = []
174
+ }
175
+ acc[key].push(account)
176
+ return acc
177
+ }, {} as ProcessedDiscoveryResult['groupedAccounts'])
178
+
179
+ // Use the signature from the first successful result
180
+ const signature = successfulResults[0].result.signature
181
+
182
+ // Set partial error message if some FIPs failed
183
+ if (failedFips.length > 0) {
184
+ const partialErrorMsg = `Some providers failed to load: ${failedFips.join(', ')}`
185
+ setErrorMessage(partialErrorMsg)
186
+ } else {
187
+ setErrorMessage(null)
188
+ }
189
+
190
+ setIsLoading(false)
191
+ return {
192
+ originalAccounts,
193
+ accounts: processedAccounts,
194
+ groupedAccounts,
195
+ signature
196
+ }
197
+ } catch (error) {
198
+ setIsLoading(false)
199
+ const typedError = error as Error & {
200
+ response?: {
201
+ status?: number;
202
+ data?: { message?: string }
203
+ }
204
+ }
205
+
206
+ if (typedError.response?.status === 401) {
207
+ setErrorMessage('Authentication error. Please log in again.')
208
+ } else if (typedError.response?.status === 403) {
209
+ setErrorMessage("You don't have permission to discover accounts.")
210
+ } else if (typedError.response?.data?.message) {
211
+ setErrorMessage(typedError.response.data.message)
212
+ } else {
213
+ setErrorMessage('Failed to discover accounts. Please try again.')
214
+ }
215
+ throw error
216
+ }
217
+ }
218
+ })
219
+
220
+ return {
221
+ ...autoDiscoveryMutation,
222
+ isLoading,
223
+ errorMessage,
224
+ clearError: () => setErrorMessage(null)
225
+ }
226
+ }
package/src/index.css CHANGED
@@ -50,6 +50,8 @@
50
50
 
51
51
  --surface: oklch(0.98 0.0017 247.84);
52
52
 
53
+ --warning-primary: oklch(0.9791 0.018 78.24);
54
+
53
55
  }
54
56
 
55
57
  .dark {
@@ -136,6 +138,7 @@
136
138
 
137
139
  --color-surface: var(--surface);
138
140
 
141
+ --color-warning-primary: var(--warning-primary);
139
142
  }
140
143
 
141
144
  @layer base {