saafe-redirection-flow 2.0.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 (225) hide show
  1. package/.github/workflows/build-and-deploy.yml +41 -0
  2. package/.gitlab-ci.yml +108 -0
  3. package/.releaserc.json +18 -0
  4. package/.storybook/main.ts +28 -0
  5. package/.storybook/preview.ts +16 -0
  6. package/.storybook/vitest.setup.ts +9 -0
  7. package/.vite/deps/@radix-ui_react-avatar.js +230 -0
  8. package/.vite/deps/@radix-ui_react-avatar.js.map +7 -0
  9. package/.vite/deps/@radix-ui_react-slot.js +12 -0
  10. package/.vite/deps/@radix-ui_react-slot.js.map +7 -0
  11. package/.vite/deps/_metadata.json +79 -0
  12. package/.vite/deps/chunk-5VGQBUCU.js +597 -0
  13. package/.vite/deps/chunk-5VGQBUCU.js.map +7 -0
  14. package/.vite/deps/chunk-DC5AMYBS.js +38 -0
  15. package/.vite/deps/chunk-DC5AMYBS.js.map +7 -0
  16. package/.vite/deps/chunk-HUIEPYH7.js +11265 -0
  17. package/.vite/deps/chunk-HUIEPYH7.js.map +7 -0
  18. package/.vite/deps/chunk-TKHB4QMX.js +281 -0
  19. package/.vite/deps/chunk-TKHB4QMX.js.map +7 -0
  20. package/.vite/deps/chunk-YLDSBLSF.js +1139 -0
  21. package/.vite/deps/chunk-YLDSBLSF.js.map +7 -0
  22. package/.vite/deps/class-variance-authority.js +63 -0
  23. package/.vite/deps/class-variance-authority.js.map +7 -0
  24. package/.vite/deps/lucide-react.js +36984 -0
  25. package/.vite/deps/lucide-react.js.map +7 -0
  26. package/.vite/deps/package.json +3 -0
  27. package/.vite/deps/react-dom_client.js +17917 -0
  28. package/.vite/deps/react-dom_client.js.map +7 -0
  29. package/.vite/deps/react-router-dom.js +452 -0
  30. package/.vite/deps/react-router-dom.js.map +7 -0
  31. package/.vite/deps/react-router.js +234 -0
  32. package/.vite/deps/react-router.js.map +7 -0
  33. package/.vite/deps/react.js +5 -0
  34. package/.vite/deps/react.js.map +7 -0
  35. package/.vite/deps/react_jsx-dev-runtime.js +470 -0
  36. package/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  37. package/CHANGELOG.md +420 -0
  38. package/LICENSE +21 -0
  39. package/README.md +129 -0
  40. package/RELEASE_CHEATSHEET.md +93 -0
  41. package/RELEASE_NOTES.md +120 -0
  42. package/components.json +21 -0
  43. package/docs/DEPLOYMENT_WORKFLOW.md +262 -0
  44. package/docs/RELEASE_GUIDE.md +591 -0
  45. package/docs/architecture.md +432 -0
  46. package/docs/components.md +199 -0
  47. package/docs/index.md +69 -0
  48. package/docs/local-release-workflow.md +234 -0
  49. package/docs/routes.md +118 -0
  50. package/docs/sdk-integration.md +325 -0
  51. package/docs/semantic-release.md +124 -0
  52. package/docs/user-flow.md +206 -0
  53. package/eslint.config.js +28 -0
  54. package/index.html +19 -0
  55. package/install.sh +198 -0
  56. package/package.json +115 -0
  57. package/public/images/bank-logo.png +0 -0
  58. package/public/saafe-icon.svg +9 -0
  59. package/src/App.tsx +171 -0
  60. package/src/__tests__/url-parameters.test.ts +82 -0
  61. package/src/assets/brand/applestore.svg +13 -0
  62. package/src/assets/brand/playstore.svg +23 -0
  63. package/src/assets/brand/saafe-color-white-logo.svg +14 -0
  64. package/src/assets/brand/saafe-icon.svg +9 -0
  65. package/src/assets/brand/saafe-logo.svg +18 -0
  66. package/src/assets/icons/check-icon-dark.svg +27 -0
  67. package/src/assets/icons/check-icon.svg +23 -0
  68. package/src/components/ErrorBoundary.tsx +132 -0
  69. package/src/components/alert/alert.tsx +27 -0
  70. package/src/components/auth/AuthGuard.tsx +76 -0
  71. package/src/components/cards/BankCard.stories.tsx +69 -0
  72. package/src/components/cards/BankCard.tsx +227 -0
  73. package/src/components/cards/OuterCard.tsx +109 -0
  74. package/src/components/cards/WrapperCard.tsx +64 -0
  75. package/src/components/documents/PrivacyContent.tsx +1 -0
  76. package/src/components/dummyFooter.tsx +29 -0
  77. package/src/components/icons/github.tsx +12 -0
  78. package/src/components/language/LanguageSwitcher.tsx +44 -0
  79. package/src/components/layouts/FrostedLayout.stories.tsx +42 -0
  80. package/src/components/layouts/FrostedLayout.tsx +333 -0
  81. package/src/components/layouts/MobileLayout.tsx +403 -0
  82. package/src/components/mobile-background.tsx +136 -0
  83. package/src/components/mobileAppDownload.tsx +30 -0
  84. package/src/components/modal/ModalComp.tsx +27 -0
  85. package/src/components/mode-toggle.tsx +36 -0
  86. package/src/components/page-header.tsx +50 -0
  87. package/src/components/session/SessionTimeoutScreen.tsx +134 -0
  88. package/src/components/session/SessionTimer.tsx +173 -0
  89. package/src/components/step-navigation.tsx +87 -0
  90. package/src/components/title/AppBar.stories.tsx +50 -0
  91. package/src/components/title/AppBar.tsx +150 -0
  92. package/src/components/title/SectionTitle.tsx +31 -0
  93. package/src/components/ui/AnimatedButton.module.css +13 -0
  94. package/src/components/ui/alert.tsx +66 -0
  95. package/src/components/ui/animatedButton.tsx +111 -0
  96. package/src/components/ui/avatar.tsx +51 -0
  97. package/src/components/ui/badge.tsx +36 -0
  98. package/src/components/ui/bottom-sheet.tsx +122 -0
  99. package/src/components/ui/button.tsx +59 -0
  100. package/src/components/ui/calendar.tsx +86 -0
  101. package/src/components/ui/card.tsx +92 -0
  102. package/src/components/ui/checkbox.stories.tsx +49 -0
  103. package/src/components/ui/checkbox.tsx +67 -0
  104. package/src/components/ui/collapsible.tsx +45 -0
  105. package/src/components/ui/dialog.tsx +134 -0
  106. package/src/components/ui/document-link.tsx +26 -0
  107. package/src/components/ui/dot-stepper.tsx +57 -0
  108. package/src/components/ui/dropdown-menu.tsx +255 -0
  109. package/src/components/ui/form.tsx +165 -0
  110. package/src/components/ui/frosted-panel.stories.tsx +86 -0
  111. package/src/components/ui/frosted-panel.tsx +276 -0
  112. package/src/components/ui/input.tsx +39 -0
  113. package/src/components/ui/label.stories.tsx +67 -0
  114. package/src/components/ui/label.tsx +23 -0
  115. package/src/components/ui/mobile-footer.tsx +54 -0
  116. package/src/components/ui/modal.tsx +90 -0
  117. package/src/components/ui/otp-input.stories.tsx +62 -0
  118. package/src/components/ui/otp-input.tsx +221 -0
  119. package/src/components/ui/platform-specific-behavior.tsx +28 -0
  120. package/src/components/ui/popover.tsx +46 -0
  121. package/src/components/ui/progress.tsx +103 -0
  122. package/src/components/ui/radio-group.tsx +45 -0
  123. package/src/components/ui/scroll-area.tsx +56 -0
  124. package/src/components/ui/sdk-params-docs.tsx +53 -0
  125. package/src/components/ui/select.tsx +159 -0
  126. package/src/components/ui/separator.tsx +28 -0
  127. package/src/components/ui/sheet.tsx +137 -0
  128. package/src/components/ui/sidebar.tsx +724 -0
  129. package/src/components/ui/skeleton.stories.tsx +50 -0
  130. package/src/components/ui/skeleton.tsx +15 -0
  131. package/src/components/ui/sonner.tsx +23 -0
  132. package/src/components/ui/step.stories.tsx +132 -0
  133. package/src/components/ui/step.tsx +234 -0
  134. package/src/components/ui/stepper-progress.tsx +136 -0
  135. package/src/components/ui/stepper.tsx +259 -0
  136. package/src/components/ui/tabs.tsx +55 -0
  137. package/src/components/ui/tooltip.tsx +61 -0
  138. package/src/components/ui/url-decode-loader.tsx +36 -0
  139. package/src/components/ui/version-display.tsx +104 -0
  140. package/src/components/ui/web-footer.tsx +36 -0
  141. package/src/config/environments.ts +99 -0
  142. package/src/config/urls.ts +53 -0
  143. package/src/const/fiTypeCategoryMap.ts +19 -0
  144. package/src/contexts/LanguageContext.tsx +41 -0
  145. package/src/contexts/RTLContext.tsx +42 -0
  146. package/src/contexts/ThemeContext.tsx +93 -0
  147. package/src/hooks/use-account-discovery.ts +205 -0
  148. package/src/hooks/use-auth-query.ts +141 -0
  149. package/src/hooks/use-fip-query.ts +72 -0
  150. package/src/hooks/use-media-query.ts +32 -0
  151. package/src/hooks/use-mobile.ts +24 -0
  152. package/src/hooks/use-page-title.tsx +48 -0
  153. package/src/hooks/use-platform.ts +52 -0
  154. package/src/hooks/use-trusted-count.ts +21 -0
  155. package/src/hooks/use-url-decode.ts +90 -0
  156. package/src/hooks/useStep.ts +170 -0
  157. package/src/index.css +154 -0
  158. package/src/interfaces/app.interfaces.ts +39 -0
  159. package/src/interfaces/services.interfaces.ts +65 -0
  160. package/src/lib/i18n.ts +68 -0
  161. package/src/lib/utils.ts +6 -0
  162. package/src/locales/en/common.json +167 -0
  163. package/src/locales/hi/common.json +137 -0
  164. package/src/locales/kn/common.json +137 -0
  165. package/src/locales/ml/common.json +137 -0
  166. package/src/locales/ta/common.json +137 -0
  167. package/src/locales/te/common.json +137 -0
  168. package/src/locales/ur/common.json +138 -0
  169. package/src/main.tsx +46 -0
  170. package/src/pages/Login.tsx +363 -0
  171. package/src/pages/accounts/AccountsToProceed.tsx +396 -0
  172. package/src/pages/accounts/Discover.tsx +76 -0
  173. package/src/pages/accounts/DiscoverAccount.tsx +751 -0
  174. package/src/pages/accounts/LinkSelectedAccounts.tsx +638 -0
  175. package/src/pages/accounts/OldUser.tsx +329 -0
  176. package/src/pages/accounts/link-accounts.tsx +913 -0
  177. package/src/pages/consent/ReviewConsent.tsx +836 -0
  178. package/src/pages/consent/rejected.tsx +253 -0
  179. package/src/pages/consent/success.tsx +220 -0
  180. package/src/providers/query-provider.tsx +24 -0
  181. package/src/providers/toast-provider.tsx +26 -0
  182. package/src/services/api/account.service.ts +296 -0
  183. package/src/services/api/auth.service.ts +206 -0
  184. package/src/services/api/axios.ts +138 -0
  185. package/src/services/api/consent.service.ts +142 -0
  186. package/src/services/api/decode.service.ts +53 -0
  187. package/src/services/api/feedback.service.ts +34 -0
  188. package/src/services/api/fip.service.ts +187 -0
  189. package/src/services/api/index.ts +9 -0
  190. package/src/services/api/public.service.ts +18 -0
  191. package/src/services/api.ts +2 -0
  192. package/src/services/postMessage.service.ts +179 -0
  193. package/src/store/NavigationBlockContext.tsx +34 -0
  194. package/src/store/auth.store.ts +79 -0
  195. package/src/store/fip.store.ts +396 -0
  196. package/src/store/mandatoryConsent.store.ts +24 -0
  197. package/src/store/redirect.store.ts +73 -0
  198. package/src/store/step.store.ts +124 -0
  199. package/src/stories/Button.stories.ts +53 -0
  200. package/src/stories/Button.tsx +37 -0
  201. package/src/stories/Configure.mdx +364 -0
  202. package/src/stories/Header.stories.ts +33 -0
  203. package/src/stories/Header.tsx +56 -0
  204. package/src/stories/Page.stories.ts +32 -0
  205. package/src/stories/Page.tsx +73 -0
  206. package/src/stories/button.css +30 -0
  207. package/src/stories/header.css +32 -0
  208. package/src/stories/page.css +68 -0
  209. package/src/styles/rtl-utils.css +90 -0
  210. package/src/styles/rtl.css +105 -0
  211. package/src/utils/api-error.ts +26 -0
  212. package/src/utils/cn.ts +10 -0
  213. package/src/utils/error-callback.ts +116 -0
  214. package/src/utils/formatAccountNumber.ts +9 -0
  215. package/src/utils/handleIdentifiers.ts +90 -0
  216. package/src/utils/posthog.ts +67 -0
  217. package/src/utils/toast-helpers.ts +61 -0
  218. package/src/vite-env.d.ts +1 -0
  219. package/stage-aa-2506251021.zip +0 -0
  220. package/tsconfig.app.json +33 -0
  221. package/tsconfig.json +13 -0
  222. package/tsconfig.node.json +24 -0
  223. package/vite.config.ts +45 -0
  224. package/vitest.shims.d.ts +1 -0
  225. package/vitest.workspace.ts +46 -0
@@ -0,0 +1,142 @@
1
+ import axiosInstance from './axios';
2
+
3
+ export interface LinkedAccount {
4
+ fiType: string;
5
+ fiGroup: string | null;
6
+ fipHandle: string;
7
+ accountRefNumber: string;
8
+ linkRefNumber: string;
9
+ fipName: string;
10
+ maskedAccNumber: string;
11
+ txnId: string;
12
+ accountType: string;
13
+ isSelfConsent: boolean;
14
+ fiuName: string | null;
15
+ consentId: string | null;
16
+ amcName: string | null;
17
+ logoUrl: string;
18
+ state: string | null;
19
+ }
20
+
21
+ export interface ConsentDetails {
22
+ consentStart: string;
23
+ consentExpiry: string;
24
+ consentMode: string;
25
+ fetchType: string;
26
+ consentTypes: string[];
27
+ fiTypes: string[];
28
+ DataConsumer: {
29
+ name: string;
30
+ id: string;
31
+ };
32
+ Customer: {
33
+ id: string;
34
+ };
35
+ Purpose: {
36
+ code: string;
37
+ refUri: string;
38
+ text: string;
39
+ Category: {
40
+ type: string;
41
+ };
42
+ };
43
+ FIDataRange: {
44
+ from: string;
45
+ to: string;
46
+ };
47
+ DataLife: {
48
+ unit: string;
49
+ value: number;
50
+ };
51
+ Frequency: {
52
+ unit: string;
53
+ value: number;
54
+ };
55
+ DataFilter: Array<{
56
+ type: string;
57
+ operator: string;
58
+ value: string;
59
+ }>;
60
+ Account: null;
61
+ ConsentHandle: string;
62
+ }
63
+
64
+ export interface ApprovalRequest {
65
+ consentHandle: string[];
66
+ constentApprovalStatus: 'APPROVED' | 'REJECTED';
67
+ accounts: {
68
+ linkRefNumber: string;
69
+ fipHandle: string;
70
+ }[];
71
+ }
72
+
73
+ export interface ConsentResponse {
74
+ success: boolean;
75
+ message: string;
76
+ data?: {
77
+ consentHandle: string;
78
+ status: string;
79
+ timestamp: string;
80
+ };
81
+ }
82
+
83
+ export interface MandatoryConsentResponse {
84
+ [key: string]: {
85
+ enabled: boolean;
86
+ mandatory: boolean;
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Service for managing consent-related operations
92
+ */
93
+ export const consentService = {
94
+ /**
95
+ * Fetch linked accounts for a consent handle
96
+ */
97
+ getLinkedAccounts: async (consentHandle: string, fipId?: string): Promise<LinkedAccount[]> => {
98
+ const params: Record<string, string> = { consentHandle };
99
+ if (fipId) {
100
+ params.fipId = fipId
101
+ }
102
+ const response = await axiosInstance.get(`/User/linkedaccount`, {
103
+ params
104
+ });
105
+ return response.data;
106
+ },
107
+
108
+ /**
109
+ * Fetch consent details for a consent handle
110
+ */
111
+ getConsentDetails: async (consentHandle: string): Promise<ConsentDetails[]> => {
112
+ const response = await axiosInstance.get(`/User/Consent/handle?consentHandle=${consentHandle}`);
113
+ return response.data;
114
+ },
115
+
116
+ /**
117
+ * Fetch mandatory consent settings for a FIU ID
118
+ */
119
+ getMandatoryConsent: async (fiuId: string): Promise<MandatoryConsentResponse> => {
120
+ try {
121
+ const response = await axiosInstance.get(`/User/mandatoryConsent?fiu_id=${fiuId}`, {
122
+ headers: {
123
+ 'Content-Type': 'application/json',
124
+ 'Accept': 'application/json',
125
+ }
126
+ });
127
+ return response.data;
128
+ }
129
+ catch (error) {
130
+ // console.error('Error fetching mandatory consent data:', error);
131
+ // throw error;
132
+ }
133
+ },
134
+
135
+ /**
136
+ * Send approval or rejection for consents
137
+ */
138
+ submitConsentVerification: async (data: ApprovalRequest): Promise<ConsentResponse> => {
139
+ const response = await axiosInstance.post('/User/Consents/Approval/Verification', data);
140
+ return response.data;
141
+ }
142
+ };
@@ -0,0 +1,53 @@
1
+ import axiosInstance from './axios'
2
+
3
+ export interface DecodeUrlParams {
4
+ fi: string
5
+ ecreq: string
6
+ reqdate: string
7
+ profile?: string
8
+ platform?: string
9
+ theme?: string
10
+ }
11
+
12
+ export interface DecodedInfo {
13
+ isAutoDiscoveryEnabled: boolean
14
+ requestorType: string
15
+ autoDiscoveryEnabled: boolean
16
+ isUserPresent: boolean
17
+ txnid: string
18
+ sessionid: string
19
+ userid: string
20
+ redirect: string
21
+ srcref: string
22
+ phoneNumber: string
23
+ pan: string
24
+ dob: string | null
25
+ fiuId: string
26
+ fiuName: string
27
+ redirectionVersion: string
28
+ fipId: string
29
+ platform?: string
30
+ theme?: string
31
+ customization: {
32
+ fiu_custom_styles: string
33
+ otherAppCustomization: string
34
+ }
35
+ fiTypesRequiredForConsent: string[]
36
+ }
37
+
38
+ /**
39
+ * Service for decoding URL parameters
40
+ */
41
+ export const decodeService = {
42
+ /**
43
+ * Decode URL parameters from the redirection URL
44
+ */
45
+ decodeUrlParams: async (params: DecodeUrlParams): Promise<DecodedInfo> => {
46
+ const response = await axiosInstance.post('/public/decode', params, {
47
+ headers: {
48
+ trackingid: ''
49
+ }
50
+ })
51
+ return response.data
52
+ }
53
+ }
@@ -0,0 +1,34 @@
1
+ import axiosInstance from './axios';
2
+
3
+ export interface FeedbackRequest {
4
+ ratings?: number;
5
+ feedback?: string;
6
+ consentHandle: string;
7
+ }
8
+
9
+ export interface FeedbackResponse {
10
+ success: boolean;
11
+ message: string;
12
+ data?: any;
13
+ }
14
+
15
+ /**
16
+ * Service for managing user feedback and ratings
17
+ */
18
+ export const feedbackService = {
19
+ /**
20
+ * Submit user feedback and ratings
21
+ */
22
+ submitRating: async (data: FeedbackRequest): Promise<FeedbackResponse> => {
23
+ const response = await axiosInstance.post('/User/FeedbackAndRatings', data);
24
+ return response.data;
25
+ },
26
+
27
+ /**
28
+ * Submit text feedback about the experience
29
+ */
30
+ submitFeedback: async (data: FeedbackRequest): Promise<FeedbackResponse> => {
31
+ const response = await axiosInstance.post('/User/FeedbackAndRatings', data);
32
+ return response.data;
33
+ }
34
+ };
@@ -0,0 +1,187 @@
1
+ import axios, { AxiosError } from 'axios'
2
+ import { useAuthStore } from '@/store/auth.store'
3
+ import { FipData } from '@/store/fip.store'
4
+ import { authService } from './auth.service'
5
+ import { handleApiError } from '@/utils/toast-helpers'
6
+
7
+ // Define API endpoints with absolute URLs
8
+ const API_BASE_URL =
9
+ import.meta.env.VITE_API_BASE_URL || 'https://sandbox.saafe.in/api/v2'
10
+ const FIP_ENDPOINTS = {
11
+ GET_FIPS: `${API_BASE_URL}/User/allFip`
12
+ }
13
+
14
+ // Define response types
15
+ export interface FipResponseData {
16
+ [key: string]: FipData[] | unknown
17
+ }
18
+
19
+ // Create axios instance with interceptors
20
+ const api = axios.create({
21
+ headers: {
22
+ 'Content-Type': 'application/json'
23
+ }
24
+ })
25
+
26
+ // Type for request with retry flag
27
+ interface RetryableRequest {
28
+ _retry?: boolean
29
+ }
30
+
31
+ // Request interceptor to add the auth token
32
+ api.interceptors.request.use(
33
+ config => {
34
+ const { accessToken } = useAuthStore.getState()
35
+
36
+ if (accessToken) {
37
+ config.headers.Authorization = `Bearer ${accessToken}`
38
+ }
39
+
40
+ return config
41
+ },
42
+ error => Promise.reject(error)
43
+ )
44
+
45
+ // Helper function for token refresh
46
+ const refreshAuthToken = async () => {
47
+ const { refreshToken } = useAuthStore.getState()
48
+
49
+ if (!refreshToken) {
50
+ throw new Error('No refresh token available')
51
+ }
52
+
53
+ const response = await authService.refreshToken(refreshToken)
54
+
55
+ useAuthStore
56
+ .getState()
57
+ .setTokens(response.access_token, response.refresh_token)
58
+
59
+ return response.access_token
60
+ }
61
+
62
+ // Response interceptor to handle API response format and token refresh
63
+ api.interceptors.response.use(
64
+ response => {
65
+ // If the response has data property, return it directly
66
+ if (response.data) {
67
+ return response
68
+ }
69
+ return response
70
+ },
71
+ async (error: AxiosError) => {
72
+ const originalRequest = error.config
73
+
74
+ // Check if the error is due to an expired token
75
+ if (
76
+ error.response?.status === 401 &&
77
+ originalRequest &&
78
+ !(originalRequest as RetryableRequest)._retry
79
+ ) {
80
+ // Mark the request as retried to prevent infinite loops
81
+ ; (originalRequest as RetryableRequest)._retry = true
82
+
83
+ try {
84
+ // Try to refresh the token
85
+ const newToken = await refreshAuthToken()
86
+
87
+ // Update the Authorization header with the new token
88
+ if (originalRequest.headers) {
89
+ originalRequest.headers.Authorization = `Bearer ${newToken}`
90
+ }
91
+
92
+ // Retry the original request with the new token
93
+ return api(originalRequest)
94
+ } catch (refreshError) {
95
+ // Logout the user on refresh token failure
96
+ useAuthStore.getState().logout()
97
+ handleApiError(refreshError, 'Session expired. Please login again.')
98
+ return Promise.reject(new Error('Session expired. Please login again.'))
99
+ }
100
+ }
101
+
102
+ // Handle API error with toast notification
103
+ handleApiError(error)
104
+ return Promise.reject(error)
105
+ }
106
+ )
107
+
108
+ export const fipService = {
109
+ /**
110
+ * Get raw FIP response to extract category structure
111
+ */
112
+ getRawFipResponse: async (
113
+ consentHandle?: string
114
+ ): Promise<FipResponseData> => {
115
+ try {
116
+ const params = consentHandle ? { consentHandle } : {}
117
+ const response = await api.get<FipResponseData>(FIP_ENDPOINTS.GET_FIPS, {
118
+ params
119
+ })
120
+ return response.data
121
+ } catch (error) {
122
+ handleApiError(error, 'Failed to fetch financial institutions')
123
+ throw error
124
+ }
125
+ },
126
+
127
+ /**
128
+ * Process the raw FIP response into a flat array
129
+ */
130
+ processFipResponse: async (
131
+ responseData: FipResponseData
132
+ ): Promise<FipData[]> => {
133
+ // Handle potential nested response structures
134
+ let fipData: FipData[] = []
135
+
136
+ if (!responseData) return []
137
+
138
+ if (Array.isArray(responseData)) {
139
+ // Direct array response
140
+ fipData = responseData as FipData[]
141
+ } else if (typeof responseData === 'object') {
142
+ // First look for category-based structure (GST, BANK, etc.)
143
+ const categoryKeys = Object.keys(responseData).filter(key =>
144
+ Array.isArray(responseData[key])
145
+ )
146
+
147
+ if (categoryKeys.length > 0) {
148
+ // Flatten all category arrays into one
149
+ fipData = categoryKeys.reduce((acc, category) => {
150
+ const categoryData = responseData[category]
151
+ if (Array.isArray(categoryData)) {
152
+ return [...acc, ...(categoryData as FipData[])]
153
+ }
154
+ return acc
155
+ }, [] as FipData[])
156
+ } else {
157
+ // Might be a nested structure, look for arrays
158
+ const potentialArrays = Object.values(responseData).filter(val =>
159
+ Array.isArray(val)
160
+ )
161
+ if (potentialArrays.length > 0) {
162
+ // Use the first array found
163
+ fipData = potentialArrays[0] as FipData[]
164
+ } else {
165
+ // If it's a single object, wrap it in an array
166
+ fipData = [responseData as unknown as FipData]
167
+ }
168
+ }
169
+ }
170
+
171
+ return fipData
172
+ },
173
+
174
+ /**
175
+ * Fetch list of Financial Information Providers (FIPs)
176
+ * Legacy method for backward compatibility
177
+ */
178
+ getFips: async (consentHandle?: string): Promise<FipData[]> => {
179
+ try {
180
+ const rawResponse = await fipService.getRawFipResponse(consentHandle)
181
+ return fipService.processFipResponse(rawResponse)
182
+ } catch (error) {
183
+ handleApiError(error, 'Failed to fetch and process FIPs')
184
+ throw error
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,9 @@
1
+ import axiosInstance from './axios';
2
+
3
+ export { axiosInstance };
4
+ export * from './auth.service';
5
+ export * from './account.service';
6
+ export * from './decode.service';
7
+ export * from './consent.service';
8
+ export * from './feedback.service';
9
+ export * from './public.service';
@@ -0,0 +1,18 @@
1
+ import axiosInstance from './axios';
2
+
3
+ export interface TrustedCountResponse {
4
+ trustedCount: string;
5
+ }
6
+
7
+ /**
8
+ * Service for public API endpoints that don't require authentication
9
+ */
10
+ export const publicService = {
11
+ /**
12
+ * Get the trusted user count
13
+ */
14
+ getTrustedCount: async (): Promise<TrustedCountResponse> => {
15
+ const response = await axiosInstance.get('/public/trusted-count');
16
+ return response.data;
17
+ }
18
+ };
@@ -0,0 +1,2 @@
1
+ // Export all API services from the api directory
2
+ export * from './api/index';
@@ -0,0 +1,179 @@
1
+ export interface PostMessageData {
2
+ type: 'CONSENT_RESULT';
3
+ status: 'success' | 'rejected' | 'error';
4
+ data?: {
5
+ consentHandle?: string;
6
+ txnid?: string;
7
+ sessionid?: string;
8
+ userid?: string;
9
+ fiuId?: string;
10
+ fiuName?: string;
11
+ reason?: string;
12
+ errorCode?: string;
13
+ rating?: number;
14
+ feedback?: string;
15
+ linkedAccounts?: Array<{
16
+ linkRefNumber: string;
17
+ fipHandle: string;
18
+ accountType: string;
19
+ maskedAccNumber: string;
20
+ }>;
21
+ consentDetails?: Array<{
22
+ consentHandle: string;
23
+ dataConsumerName: string;
24
+ purpose: string;
25
+ }>;
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Service for communicating with parent iframe
31
+ */
32
+ export const postMessageService = {
33
+ /**
34
+ * Send a message to the parent iframe with retries and multiple formats
35
+ */
36
+ sendToParent: (messageData: PostMessageData) => {
37
+ try {
38
+ // Only send if we're in an iframe
39
+ if (window.parent && window.parent !== window) {
40
+ console.log('PostMessage being sent to parent:', messageData);
41
+
42
+ // Send the primary message format
43
+ window.parent.postMessage(messageData, '*');
44
+
45
+ // Also send alternative formats for compatibility
46
+ if (messageData.status === 'success') {
47
+ // Send AA_COMPLETED format for compatibility
48
+ window.parent.postMessage({
49
+ type: 'AA_COMPLETED',
50
+ status: 'success',
51
+ data: messageData.data
52
+ }, '*');
53
+
54
+ // Send AA_SUCCESS format for compatibility
55
+ window.parent.postMessage({
56
+ type: 'AA_SUCCESS',
57
+ data: messageData.data
58
+ }, '*');
59
+ } else if (messageData.status === 'rejected') {
60
+ // Send AA_REJECTED format for compatibility
61
+ window.parent.postMessage({
62
+ type: 'AA_REJECTED',
63
+ status: 'rejected',
64
+ data: messageData.data
65
+ }, '*');
66
+ } else if (messageData.status === 'error') {
67
+ // Send AA_ERROR format for compatibility
68
+ window.parent.postMessage({
69
+ type: 'AA_ERROR',
70
+ status: 'error',
71
+ data: messageData.data
72
+ }, '*');
73
+
74
+ // Send AA_FAILED format for compatibility
75
+ window.parent.postMessage({
76
+ type: 'AA_FAILED',
77
+ status: 'failed',
78
+ data: messageData.data
79
+ }, '*');
80
+ }
81
+
82
+ console.log('PostMessage sent successfully to parent');
83
+ } else {
84
+ console.log('Not in iframe, message would be:', messageData);
85
+ console.log('Window parent check:', window.parent, window.parent === window);
86
+ }
87
+ } catch (error) {
88
+ console.error('Error sending postMessage:', error);
89
+ }
90
+ },
91
+
92
+ /**
93
+ * Send message with delay to ensure parent is ready
94
+ */
95
+ sendWithDelay: (messageData: PostMessageData, delay: number = 1000) => {
96
+ setTimeout(() => {
97
+ postMessageService.sendToParent(messageData);
98
+ }, delay);
99
+ },
100
+
101
+ /**
102
+ * Send message multiple times to ensure delivery
103
+ */
104
+ sendWithRetries: (messageData: PostMessageData, retries: number = 3, interval: number = 500) => {
105
+ // Send immediately
106
+ postMessageService.sendToParent(messageData);
107
+
108
+ // Send with delays
109
+ for (let i = 1; i <= retries; i++) {
110
+ setTimeout(() => {
111
+ postMessageService.sendToParent(messageData);
112
+ }, interval * i);
113
+ }
114
+ },
115
+
116
+ /**
117
+ * Send success message
118
+ */
119
+ sendSuccess: (data?: PostMessageData['data']) => {
120
+ const messageData = {
121
+ type: 'CONSENT_RESULT' as const,
122
+ status: 'success' as const,
123
+ data
124
+ };
125
+
126
+ // Send immediately and with retries to ensure delivery
127
+ postMessageService.sendWithRetries(messageData, 2, 1000);
128
+ },
129
+
130
+ /**
131
+ * Send rejection message
132
+ */
133
+ sendRejected: (data?: PostMessageData['data']) => {
134
+ const messageData = {
135
+ type: 'CONSENT_RESULT' as const,
136
+ status: 'rejected' as const,
137
+ data
138
+ };
139
+
140
+ // Send immediately and with retries to ensure delivery
141
+ postMessageService.sendWithRetries(messageData, 2, 1000);
142
+ },
143
+
144
+ /**
145
+ * Send error message
146
+ */
147
+ sendError: (data?: PostMessageData['data']) => {
148
+ const messageData = {
149
+ type: 'CONSENT_RESULT' as const,
150
+ status: 'error' as const,
151
+ data
152
+ };
153
+
154
+ // Send immediately and with retries to ensure delivery
155
+ postMessageService.sendWithRetries(messageData, 2, 1000);
156
+ },
157
+
158
+ /**
159
+ * Debug function to test postMessage connectivity
160
+ */
161
+ testConnection: () => {
162
+ console.log('Testing postMessage connection...');
163
+ console.log('Window parent:', window.parent);
164
+ console.log('Is in iframe:', window.parent !== window);
165
+ console.log('Window origin:', window.location.origin);
166
+
167
+ if (window.parent && window.parent !== window) {
168
+ // Send a test message
169
+ window.parent.postMessage({
170
+ type: 'TEST_MESSAGE',
171
+ timestamp: new Date().toISOString(),
172
+ origin: window.location.origin
173
+ }, '*');
174
+ console.log('Test message sent to parent');
175
+ } else {
176
+ console.log('Not in iframe - cannot send test message');
177
+ }
178
+ }
179
+ };
@@ -0,0 +1,34 @@
1
+ import React, { createContext, useRef, useContext } from 'react';
2
+
3
+ type NavigationBlockContextType = {
4
+ allowNextNavigation: () => void;
5
+ shouldAllowNavigation: () => boolean;
6
+ };
7
+
8
+ const NavigationBlockContext = createContext<NavigationBlockContextType | null>(null);
9
+
10
+ export const NavigationBlockProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
11
+ const allowNavigationRef = useRef(false);
12
+
13
+ const allowNextNavigation = () => {
14
+ allowNavigationRef.current = true;
15
+ };
16
+
17
+ const shouldAllowNavigation = () => {
18
+ const allowed = allowNavigationRef.current;
19
+ allowNavigationRef.current = false; // reset after checking
20
+ return allowed;
21
+ };
22
+
23
+ return (
24
+ <NavigationBlockContext.Provider value={{ allowNextNavigation, shouldAllowNavigation }}>
25
+ {children}
26
+ </NavigationBlockContext.Provider>
27
+ );
28
+ };
29
+
30
+ export const useNavigationBlock = () => {
31
+ const context = useContext(NavigationBlockContext);
32
+ if (!context) throw new Error('useNavigationBlock must be used within NavigationBlockProvider');
33
+ return context;
34
+ };