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.
- package/.github/workflows/build-and-deploy.yml +41 -0
- package/.gitlab-ci.yml +108 -0
- package/.releaserc.json +18 -0
- package/.storybook/main.ts +28 -0
- package/.storybook/preview.ts +16 -0
- package/.storybook/vitest.setup.ts +9 -0
- package/.vite/deps/@radix-ui_react-avatar.js +230 -0
- package/.vite/deps/@radix-ui_react-avatar.js.map +7 -0
- package/.vite/deps/@radix-ui_react-slot.js +12 -0
- package/.vite/deps/@radix-ui_react-slot.js.map +7 -0
- package/.vite/deps/_metadata.json +79 -0
- package/.vite/deps/chunk-5VGQBUCU.js +597 -0
- package/.vite/deps/chunk-5VGQBUCU.js.map +7 -0
- package/.vite/deps/chunk-DC5AMYBS.js +38 -0
- package/.vite/deps/chunk-DC5AMYBS.js.map +7 -0
- package/.vite/deps/chunk-HUIEPYH7.js +11265 -0
- package/.vite/deps/chunk-HUIEPYH7.js.map +7 -0
- package/.vite/deps/chunk-TKHB4QMX.js +281 -0
- package/.vite/deps/chunk-TKHB4QMX.js.map +7 -0
- package/.vite/deps/chunk-YLDSBLSF.js +1139 -0
- package/.vite/deps/chunk-YLDSBLSF.js.map +7 -0
- package/.vite/deps/class-variance-authority.js +63 -0
- package/.vite/deps/class-variance-authority.js.map +7 -0
- package/.vite/deps/lucide-react.js +36984 -0
- package/.vite/deps/lucide-react.js.map +7 -0
- package/.vite/deps/package.json +3 -0
- package/.vite/deps/react-dom_client.js +17917 -0
- package/.vite/deps/react-dom_client.js.map +7 -0
- package/.vite/deps/react-router-dom.js +452 -0
- package/.vite/deps/react-router-dom.js.map +7 -0
- package/.vite/deps/react-router.js +234 -0
- package/.vite/deps/react-router.js.map +7 -0
- package/.vite/deps/react.js +5 -0
- package/.vite/deps/react.js.map +7 -0
- package/.vite/deps/react_jsx-dev-runtime.js +470 -0
- package/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/CHANGELOG.md +420 -0
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/RELEASE_CHEATSHEET.md +93 -0
- package/RELEASE_NOTES.md +120 -0
- package/components.json +21 -0
- package/docs/DEPLOYMENT_WORKFLOW.md +262 -0
- package/docs/RELEASE_GUIDE.md +591 -0
- package/docs/architecture.md +432 -0
- package/docs/components.md +199 -0
- package/docs/index.md +69 -0
- package/docs/local-release-workflow.md +234 -0
- package/docs/routes.md +118 -0
- package/docs/sdk-integration.md +325 -0
- package/docs/semantic-release.md +124 -0
- package/docs/user-flow.md +206 -0
- package/eslint.config.js +28 -0
- package/index.html +19 -0
- package/install.sh +198 -0
- package/package.json +115 -0
- package/public/images/bank-logo.png +0 -0
- package/public/saafe-icon.svg +9 -0
- package/src/App.tsx +171 -0
- package/src/__tests__/url-parameters.test.ts +82 -0
- package/src/assets/brand/applestore.svg +13 -0
- package/src/assets/brand/playstore.svg +23 -0
- package/src/assets/brand/saafe-color-white-logo.svg +14 -0
- package/src/assets/brand/saafe-icon.svg +9 -0
- package/src/assets/brand/saafe-logo.svg +18 -0
- package/src/assets/icons/check-icon-dark.svg +27 -0
- package/src/assets/icons/check-icon.svg +23 -0
- package/src/components/ErrorBoundary.tsx +132 -0
- package/src/components/alert/alert.tsx +27 -0
- package/src/components/auth/AuthGuard.tsx +76 -0
- package/src/components/cards/BankCard.stories.tsx +69 -0
- package/src/components/cards/BankCard.tsx +227 -0
- package/src/components/cards/OuterCard.tsx +109 -0
- package/src/components/cards/WrapperCard.tsx +64 -0
- package/src/components/documents/PrivacyContent.tsx +1 -0
- package/src/components/dummyFooter.tsx +29 -0
- package/src/components/icons/github.tsx +12 -0
- package/src/components/language/LanguageSwitcher.tsx +44 -0
- package/src/components/layouts/FrostedLayout.stories.tsx +42 -0
- package/src/components/layouts/FrostedLayout.tsx +333 -0
- package/src/components/layouts/MobileLayout.tsx +403 -0
- package/src/components/mobile-background.tsx +136 -0
- package/src/components/mobileAppDownload.tsx +30 -0
- package/src/components/modal/ModalComp.tsx +27 -0
- package/src/components/mode-toggle.tsx +36 -0
- package/src/components/page-header.tsx +50 -0
- package/src/components/session/SessionTimeoutScreen.tsx +134 -0
- package/src/components/session/SessionTimer.tsx +173 -0
- package/src/components/step-navigation.tsx +87 -0
- package/src/components/title/AppBar.stories.tsx +50 -0
- package/src/components/title/AppBar.tsx +150 -0
- package/src/components/title/SectionTitle.tsx +31 -0
- package/src/components/ui/AnimatedButton.module.css +13 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/animatedButton.tsx +111 -0
- package/src/components/ui/avatar.tsx +51 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/bottom-sheet.tsx +122 -0
- package/src/components/ui/button.tsx +59 -0
- package/src/components/ui/calendar.tsx +86 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox.stories.tsx +49 -0
- package/src/components/ui/checkbox.tsx +67 -0
- package/src/components/ui/collapsible.tsx +45 -0
- package/src/components/ui/dialog.tsx +134 -0
- package/src/components/ui/document-link.tsx +26 -0
- package/src/components/ui/dot-stepper.tsx +57 -0
- package/src/components/ui/dropdown-menu.tsx +255 -0
- package/src/components/ui/form.tsx +165 -0
- package/src/components/ui/frosted-panel.stories.tsx +86 -0
- package/src/components/ui/frosted-panel.tsx +276 -0
- package/src/components/ui/input.tsx +39 -0
- package/src/components/ui/label.stories.tsx +67 -0
- package/src/components/ui/label.tsx +23 -0
- package/src/components/ui/mobile-footer.tsx +54 -0
- package/src/components/ui/modal.tsx +90 -0
- package/src/components/ui/otp-input.stories.tsx +62 -0
- package/src/components/ui/otp-input.tsx +221 -0
- package/src/components/ui/platform-specific-behavior.tsx +28 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/progress.tsx +103 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/sdk-params-docs.tsx +53 -0
- package/src/components/ui/select.tsx +159 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +137 -0
- package/src/components/ui/sidebar.tsx +724 -0
- package/src/components/ui/skeleton.stories.tsx +50 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/sonner.tsx +23 -0
- package/src/components/ui/step.stories.tsx +132 -0
- package/src/components/ui/step.tsx +234 -0
- package/src/components/ui/stepper-progress.tsx +136 -0
- package/src/components/ui/stepper.tsx +259 -0
- package/src/components/ui/tabs.tsx +55 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/components/ui/url-decode-loader.tsx +36 -0
- package/src/components/ui/version-display.tsx +104 -0
- package/src/components/ui/web-footer.tsx +36 -0
- package/src/config/environments.ts +99 -0
- package/src/config/urls.ts +53 -0
- package/src/const/fiTypeCategoryMap.ts +19 -0
- package/src/contexts/LanguageContext.tsx +41 -0
- package/src/contexts/RTLContext.tsx +42 -0
- package/src/contexts/ThemeContext.tsx +93 -0
- package/src/hooks/use-account-discovery.ts +205 -0
- package/src/hooks/use-auth-query.ts +141 -0
- package/src/hooks/use-fip-query.ts +72 -0
- package/src/hooks/use-media-query.ts +32 -0
- package/src/hooks/use-mobile.ts +24 -0
- package/src/hooks/use-page-title.tsx +48 -0
- package/src/hooks/use-platform.ts +52 -0
- package/src/hooks/use-trusted-count.ts +21 -0
- package/src/hooks/use-url-decode.ts +90 -0
- package/src/hooks/useStep.ts +170 -0
- package/src/index.css +154 -0
- package/src/interfaces/app.interfaces.ts +39 -0
- package/src/interfaces/services.interfaces.ts +65 -0
- package/src/lib/i18n.ts +68 -0
- package/src/lib/utils.ts +6 -0
- package/src/locales/en/common.json +167 -0
- package/src/locales/hi/common.json +137 -0
- package/src/locales/kn/common.json +137 -0
- package/src/locales/ml/common.json +137 -0
- package/src/locales/ta/common.json +137 -0
- package/src/locales/te/common.json +137 -0
- package/src/locales/ur/common.json +138 -0
- package/src/main.tsx +46 -0
- package/src/pages/Login.tsx +363 -0
- package/src/pages/accounts/AccountsToProceed.tsx +396 -0
- package/src/pages/accounts/Discover.tsx +76 -0
- package/src/pages/accounts/DiscoverAccount.tsx +751 -0
- package/src/pages/accounts/LinkSelectedAccounts.tsx +638 -0
- package/src/pages/accounts/OldUser.tsx +329 -0
- package/src/pages/accounts/link-accounts.tsx +913 -0
- package/src/pages/consent/ReviewConsent.tsx +836 -0
- package/src/pages/consent/rejected.tsx +253 -0
- package/src/pages/consent/success.tsx +220 -0
- package/src/providers/query-provider.tsx +24 -0
- package/src/providers/toast-provider.tsx +26 -0
- package/src/services/api/account.service.ts +296 -0
- package/src/services/api/auth.service.ts +206 -0
- package/src/services/api/axios.ts +138 -0
- package/src/services/api/consent.service.ts +142 -0
- package/src/services/api/decode.service.ts +53 -0
- package/src/services/api/feedback.service.ts +34 -0
- package/src/services/api/fip.service.ts +187 -0
- package/src/services/api/index.ts +9 -0
- package/src/services/api/public.service.ts +18 -0
- package/src/services/api.ts +2 -0
- package/src/services/postMessage.service.ts +179 -0
- package/src/store/NavigationBlockContext.tsx +34 -0
- package/src/store/auth.store.ts +79 -0
- package/src/store/fip.store.ts +396 -0
- package/src/store/mandatoryConsent.store.ts +24 -0
- package/src/store/redirect.store.ts +73 -0
- package/src/store/step.store.ts +124 -0
- package/src/stories/Button.stories.ts +53 -0
- package/src/stories/Button.tsx +37 -0
- package/src/stories/Configure.mdx +364 -0
- package/src/stories/Header.stories.ts +33 -0
- package/src/stories/Header.tsx +56 -0
- package/src/stories/Page.stories.ts +32 -0
- package/src/stories/Page.tsx +73 -0
- package/src/stories/button.css +30 -0
- package/src/stories/header.css +32 -0
- package/src/stories/page.css +68 -0
- package/src/styles/rtl-utils.css +90 -0
- package/src/styles/rtl.css +105 -0
- package/src/utils/api-error.ts +26 -0
- package/src/utils/cn.ts +10 -0
- package/src/utils/error-callback.ts +116 -0
- package/src/utils/formatAccountNumber.ts +9 -0
- package/src/utils/handleIdentifiers.ts +90 -0
- package/src/utils/posthog.ts +67 -0
- package/src/utils/toast-helpers.ts +61 -0
- package/src/vite-env.d.ts +1 -0
- package/stage-aa-2506251021.zip +0 -0
- package/tsconfig.app.json +33 -0
- package/tsconfig.json +13 -0
- package/tsconfig.node.json +24 -0
- package/vite.config.ts +45 -0
- package/vitest.shims.d.ts +1 -0
- 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,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
|
+
};
|