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,79 @@
1
+ import { create } from 'zustand';
2
+ import { createJSONStorage, persist } from 'zustand/middleware';
3
+
4
+ export interface AuthUser {
5
+ firstName: string;
6
+ lastName: string;
7
+ vuaId: string;
8
+ phoneNumber: string;
9
+ isTwoFactorEnabled: boolean | null;
10
+ isBioMetricsEnabled: boolean | null;
11
+ }
12
+
13
+ export interface AuthState {
14
+ user: AuthUser | null;
15
+ accessToken: string | null;
16
+ refreshToken: string | null;
17
+ otpUniqueId: string | null;
18
+ isAuthenticated: boolean;
19
+ isLoading: boolean;
20
+ error: string | null;
21
+
22
+ // Actions
23
+ setUser: (user: AuthUser | null) => void;
24
+ setTokens: (accessToken: string, refreshToken: string) => void;
25
+ setOtpUniqueId: (id: string | null) => void;
26
+ setIsLoading: (isLoading: boolean) => void;
27
+ setError: (error: string | null) => void;
28
+ logout: () => void;
29
+ }
30
+
31
+ export const useAuthStore = create<AuthState>()(
32
+ persist(
33
+ (set) => ({
34
+ user: null,
35
+ accessToken: null,
36
+ refreshToken: null,
37
+ otpUniqueId: null,
38
+ isAuthenticated: false,
39
+ isLoading: false,
40
+ error: null,
41
+
42
+ setUser: (user) => set({
43
+ user,
44
+ isAuthenticated: !!user
45
+ }),
46
+
47
+ setTokens: (accessToken, refreshToken) => set({
48
+ accessToken,
49
+ refreshToken,
50
+ isAuthenticated: true
51
+ }),
52
+
53
+ setOtpUniqueId: (otpUniqueId) => set({ otpUniqueId }),
54
+
55
+ setIsLoading: (isLoading) => set({ isLoading }),
56
+
57
+ setError: (error) => set({ error }),
58
+
59
+ logout: () => set({
60
+ user: null,
61
+ accessToken: null,
62
+ refreshToken: null,
63
+ otpUniqueId: null,
64
+ isAuthenticated: false,
65
+ error: null
66
+ })
67
+ }),
68
+ {
69
+ name: 'saafe-auth-storage',
70
+ storage: createJSONStorage(() => sessionStorage),
71
+ partialize: (state) => ({
72
+ user: state.user,
73
+ accessToken: state.accessToken,
74
+ refreshToken: state.refreshToken,
75
+ isAuthenticated: state.isAuthenticated
76
+ })
77
+ }
78
+ )
79
+ );
@@ -0,0 +1,396 @@
1
+ import { create } from 'zustand'
2
+ import { createJSONStorage, persist } from 'zustand/middleware'
3
+ import { StepItem } from '@/components/step-navigation'
4
+ import { AutoDiscoveryResponse } from '@/services/api'
5
+ import { fiTypeCategoryMap } from '@/const/fiTypeCategoryMap'
6
+
7
+ // FIP data interfaces
8
+ export interface Identifier {
9
+ category: string
10
+ type: string
11
+ }
12
+
13
+ export interface FipData {
14
+ fiTypeList: string[]
15
+ isEnabled: boolean
16
+ isOnBoarded: boolean
17
+ logoUrl: string
18
+ id: string
19
+ name: string
20
+ Identifiers: Identifier[]
21
+ host?: string
22
+ hostV2?: string | null
23
+ version?: string
24
+ }
25
+
26
+ export interface GroupedFips {
27
+ [category: string]: FipData[]
28
+ }
29
+
30
+ // Define main journey steps
31
+ export const MAIN_STEPS = {
32
+ LOGIN: 'login',
33
+ LINK_ACCOUNTS: 'link_accounts',
34
+ REVIEW_CONSENT: 'review_consent'
35
+ }
36
+
37
+ // Define flow steps for each category
38
+ export const CATEGORY_STEPS = {
39
+ LIST: 'list',
40
+ DISCOVERY: 'discovery',
41
+ LINKING: 'linking'
42
+ }
43
+
44
+ export interface FipState {
45
+ fips: FipData[]
46
+ groupedFips: Record<string, FipData[]>
47
+ selectedFips: Record<string, string[]>
48
+ selectedMobileNumber?: string
49
+ activeCategory: string | null
50
+ categories: string[]
51
+ completedCategories: string[]
52
+ currentCategoryStep: CATEGORY_STEPS
53
+ isLoading: boolean
54
+ error: string | null
55
+ selectedAccountToLink: any[]
56
+ signature: string | null
57
+ originalAccounts: AutoDiscoveryResponse['Accounts']
58
+ accountsStatusArray: any[]
59
+ identifiers: any[]
60
+ accountForConsent: any[]
61
+ accountsToNotify: any[]
62
+
63
+ // Navigation state
64
+ currentStep: string
65
+ currentChildStep: string | null
66
+ navigationSteps: StepItem[]
67
+
68
+ // Actions
69
+ setFips: (
70
+ fips: FipData[],
71
+ responseCategories?: string[],
72
+ preCategorizedFips?: GroupedFips
73
+ ) => void
74
+ setSelectedFips: (category: string, fipIds: string[]) => void
75
+ addSelectedFip: (category: string, fipId: string) => void
76
+ removeSelectedFip: (category: string, fipId: string) => void
77
+ setSelectedMobileNumber: (mobileNumber: string) => void
78
+ setActiveCategory: (category: string | null) => void
79
+ setCategoryStep: (step: string) => void
80
+ completeCategory: (category: string) => void
81
+ setCurrentStep: (stepId: string) => void
82
+ setCurrentChildStep: (childStepId: string | null) => void
83
+ clearSelections: () => void
84
+ setIsLoading: (isLoading: boolean) => void
85
+ setError: (error: string | null) => void
86
+ setSignature: (signature: string | null) => void
87
+ setSelectedAccountToLink: (account: any[]) => void
88
+ setOriginalAccounts: (
89
+ originalAccounts: AutoDiscoveryResponse['Accounts']
90
+ ) => void
91
+ setAccountsStatusArray: (data: any[]) => void
92
+ setIdentifiers: (identifiersList: any[]) => void
93
+ setAccountForConsent: (accountForConsent: any[]) => void
94
+ setAccountsToNotify: (data: string) => void
95
+ }
96
+
97
+ // Initial navigation steps structure
98
+ const initialNavigationSteps: StepItem[] = [
99
+ {
100
+ id: MAIN_STEPS.LOGIN,
101
+ title: 'Login',
102
+ isActive: false,
103
+ isCompleted: true
104
+ },
105
+ {
106
+ id: MAIN_STEPS.LINK_ACCOUNTS,
107
+ title: 'Link Accounts',
108
+ isActive: true,
109
+ isCompleted: false,
110
+ childSteps: []
111
+ },
112
+ {
113
+ id: MAIN_STEPS.REVIEW_CONSENT,
114
+ title: 'Review Consent',
115
+ isActive: false,
116
+ isCompleted: false
117
+ }
118
+ ]
119
+
120
+ // Helper function to extract categories
121
+ export const extractCategories = (groupedFips: string[]): string[] => {
122
+ const category = new Set()
123
+ console.log('groupedFips', groupedFips);
124
+
125
+ groupedFips.forEach((i) => {
126
+ Object.keys(fiTypeCategoryMap).forEach((fiType) => {
127
+ if (fiTypeCategoryMap?.[fiType].includes(i)) {
128
+ category.add(fiType)
129
+ return
130
+ }
131
+ })
132
+ })
133
+
134
+ const result = [...category]
135
+ if (result.length > 1) {
136
+ const order = Object.keys(fiTypeCategoryMap)
137
+ return result.sort((a, b) => order.indexOf(a) - order.indexOf(b)) as unknown as string[]
138
+ }
139
+
140
+ return result as unknown as string[]
141
+ }
142
+
143
+ // Helper function to create child steps from categories
144
+ const createChildSteps = (categories: string[]): StepItem[] => {
145
+ return categories.map((category, index) => ({
146
+ id: category,
147
+ title: category
148
+ .split('_')
149
+ .map(word => word.charAt(0) + word.slice(1).toLowerCase())
150
+ .join(' '),
151
+ isActive: index === 0,
152
+ isCompleted: false
153
+ }))
154
+ }
155
+
156
+ // Helper function to update navigation steps with child steps
157
+ const updateNavigationSteps = (
158
+ steps: StepItem[],
159
+ childSteps: StepItem[],
160
+ currentChildStep: string | null
161
+ ): StepItem[] => {
162
+ return steps.map(step => {
163
+ if (step.id === MAIN_STEPS.LINK_ACCOUNTS) {
164
+ return {
165
+ ...step,
166
+ childSteps: childSteps.map(childStep => ({
167
+ ...childStep,
168
+ isActive: childStep.id === currentChildStep
169
+ }))
170
+ }
171
+ }
172
+ return step
173
+ })
174
+ }
175
+
176
+ export const useFipStore = create<FipState>()(
177
+ persist(
178
+ (set, get) => ({
179
+ fips: [],
180
+ groupedFips: {},
181
+ selectedFips: {},
182
+ selectedMobileNumber: undefined,
183
+ activeCategory: null,
184
+ categories: [],
185
+ completedCategories: [],
186
+ currentCategoryStep: CATEGORY_STEPS.LIST,
187
+ isLoading: false,
188
+ error: null,
189
+ selectedAccountToLink: [],
190
+ signature: null,
191
+ originalAccounts: [],
192
+ accountsStatusArray: [],
193
+ identifiers: [],
194
+ accountForConsent: [],
195
+ accountsToNotify: [],
196
+
197
+ // Navigation state
198
+ currentStep: MAIN_STEPS.LINK_ACCOUNTS,
199
+ currentChildStep: null,
200
+ navigationSteps: initialNavigationSteps,
201
+
202
+ setFips: (fips, responseCategories, preCategorizedFips) => {
203
+ // If pre-categorized FIPs are provided, use them directly
204
+ const groupedFips = preCategorizedFips || {}
205
+ const categoryList = responseCategories || Object.keys(groupedFips || {})
206
+ const categories = extractCategories(categoryList as unknown as string[])
207
+
208
+ // Initialize selectedFips with empty arrays for each category
209
+ const selectedFips: { [category: string]: string[] } = {}
210
+ categories.forEach(category => {
211
+ selectedFips[category] = []
212
+ })
213
+
214
+ // Create child steps from categories
215
+ const childSteps = createChildSteps(categories)
216
+ const currentChildStep = categories.length > 0 ? categories[0] : null
217
+
218
+ // Update navigation steps
219
+ const navigationSteps = updateNavigationSteps(
220
+ initialNavigationSteps,
221
+ childSteps,
222
+ currentChildStep
223
+ )
224
+
225
+ set({
226
+ fips,
227
+ groupedFips,
228
+ categories,
229
+ selectedFips,
230
+ activeCategory: currentChildStep,
231
+ currentChildStep,
232
+ navigationSteps,
233
+ currentCategoryStep: CATEGORY_STEPS.LIST
234
+ })
235
+ },
236
+
237
+ setSelectedFips: (category, fipIds) =>
238
+ set(state => ({
239
+ selectedFips: {
240
+ ...state.selectedFips,
241
+ [category]: fipIds
242
+ }
243
+ })),
244
+
245
+ setSelectedAccountToLink: accountIds =>
246
+ set({ selectedAccountToLink: accountIds }),
247
+
248
+ setSignature: signature => set({ signature }),
249
+
250
+ setIdentifiers: identifiers => set({ identifiers }),
251
+
252
+ setOriginalAccounts: originalAccounts => set({ originalAccounts }),
253
+
254
+ setAccountsStatusArray: data => set({ accountsStatusArray: data }),
255
+
256
+ addSelectedFip: (category, fipId) =>
257
+ set(state => {
258
+ const currentSelected = state.selectedFips[category] || []
259
+ if (currentSelected.includes(fipId)) return state
260
+
261
+ return {
262
+ selectedFips: {
263
+ ...state.selectedFips,
264
+ [category]: [...currentSelected, fipId]
265
+ }
266
+ }
267
+ }),
268
+
269
+ removeSelectedFip: (category, fipId) =>
270
+ set(state => ({
271
+ selectedFips: {
272
+ ...state.selectedFips,
273
+ [category]: (state.selectedFips[category] || []).filter(
274
+ id => id !== fipId
275
+ )
276
+ }
277
+ })),
278
+
279
+ setSelectedMobileNumber: mobileNumber =>
280
+ set({ selectedMobileNumber: mobileNumber }),
281
+
282
+ setActiveCategory: (category: string | null) => {
283
+ const { completedCategories } = get()
284
+ const isCompleted = category && completedCategories.includes(category)
285
+
286
+ // Update the active category and current child step
287
+ set({
288
+ activeCategory: category,
289
+ currentChildStep: category,
290
+ // If this category was already completed, set to LINKING step
291
+ // Otherwise start at LIST step
292
+ currentCategoryStep: isCompleted
293
+ ? CATEGORY_STEPS.LINKING
294
+ : CATEGORY_STEPS.LIST
295
+ })
296
+ },
297
+
298
+ setCategoryStep: step => set({ currentCategoryStep: step }),
299
+
300
+ completeCategory: (category: string) => {
301
+ const { completedCategories } = get()
302
+
303
+ // Only add if not already in the completed list
304
+ if (!completedCategories.includes(category)) {
305
+ set({
306
+ completedCategories: [...completedCategories, category]
307
+ })
308
+ }
309
+
310
+ // After completing, set the category step to LIST for the next category
311
+ set({ currentCategoryStep: CATEGORY_STEPS.LIST })
312
+ },
313
+
314
+ setAccountForConsent: accountForConsent => set({ accountForConsent }),
315
+
316
+ setCurrentStep: stepId =>
317
+ set(state => {
318
+ const updatedSteps = state.navigationSteps.map(step => ({
319
+ ...step,
320
+ isActive: step.id === stepId,
321
+ // Mark previous steps as completed
322
+ isCompleted:
323
+ step.id !== stepId &&
324
+ state.navigationSteps.findIndex(s => s.id === step.id) <
325
+ state.navigationSteps.findIndex(s => s.id === stepId)
326
+ }))
327
+
328
+ return {
329
+ currentStep: stepId,
330
+ navigationSteps: updatedSteps
331
+ }
332
+ }),
333
+
334
+ setCurrentChildStep: childStepId =>
335
+ set(state => {
336
+ if (!childStepId) return state
337
+
338
+ // Update the active category
339
+ const updatedNavigationSteps = updateNavigationSteps(
340
+ state.navigationSteps,
341
+ state.navigationSteps.find(s => s.id === MAIN_STEPS.LINK_ACCOUNTS)
342
+ ?.childSteps || [],
343
+ childStepId
344
+ )
345
+
346
+ return {
347
+ activeCategory: childStepId,
348
+ currentChildStep: childStepId,
349
+ navigationSteps: updatedNavigationSteps,
350
+ // Reset the category step when changing child steps
351
+ currentCategoryStep: CATEGORY_STEPS.LIST
352
+ }
353
+ }),
354
+
355
+ clearSelections: () =>
356
+ set(state => {
357
+ const emptySelections: { [category: string]: string[] } = {}
358
+ state.categories.forEach(category => {
359
+ emptySelections[category] = []
360
+ })
361
+ return { selectedFips: emptySelections }
362
+ }),
363
+
364
+ setIsLoading: isLoading => set({ isLoading }),
365
+
366
+ setAccountsToNotify: (data: string | null) => {
367
+ console.log('data', data);
368
+
369
+ set(state => ({
370
+ accountsToNotify: [...state?.accountsToNotify, data]
371
+ }))
372
+ },
373
+
374
+ setError: error => set({ error })
375
+ }),
376
+ {
377
+ name: 'saafe-fip-storage',
378
+ storage: createJSONStorage(() => sessionStorage),
379
+ partialize: state => ({
380
+ selectedFips: state.selectedFips,
381
+ activeCategory: state.activeCategory,
382
+ selectedMobileNumber: state.selectedMobileNumber,
383
+ currentStep: state.currentStep,
384
+ categories: state.categories,
385
+ currentChildStep: state.currentChildStep,
386
+ completedCategories: state.completedCategories,
387
+ accountsStatusArray: state.accountsStatusArray,
388
+ selectedAccountToLink: state.selectedAccountToLink,
389
+ originalAccounts: state.originalAccounts,
390
+ identifiers: state.identifiers,
391
+ signature: state.signature,
392
+ accountForConsent: state.accountForConsent
393
+ })
394
+ }
395
+ )
396
+ )
@@ -0,0 +1,24 @@
1
+ import { create } from 'zustand'
2
+ import { persist } from 'zustand/middleware'
3
+
4
+ export type MandatoryConsentData = {
5
+ enabled: boolean
6
+ mandatory: boolean
7
+ }
8
+
9
+ export type MandatoryConsentState = {
10
+ mandatoryConsent: Record<string, MandatoryConsentData>
11
+ setMandatoryConsent: (data: Record<string, MandatoryConsentData>) => void
12
+ }
13
+
14
+ export const useMandatoryConsentStore = create<MandatoryConsentState>()(
15
+ persist(
16
+ (set) => ({
17
+ mandatoryConsent: {},
18
+ setMandatoryConsent: (data) => set({ mandatoryConsent: data }),
19
+ }),
20
+ {
21
+ name: 'mandatory-consent-storage',
22
+ }
23
+ )
24
+ )
@@ -0,0 +1,73 @@
1
+ import { create } from "zustand";
2
+ import { createJSONStorage, persist } from "zustand/middleware";
3
+ import { DecodedInfo } from "@/services/api/decode.service";
4
+
5
+ export interface RedirectState {
6
+ decodedInfo: DecodedInfo | null;
7
+ isDecoded: boolean;
8
+ isDecodingError: boolean;
9
+ error: string | null;
10
+
11
+ // Actions
12
+ setDecodedInfo: (info: DecodedInfo) => void;
13
+ setDecodingError: (error: string) => void;
14
+ clearDecodedInfo: () => void;
15
+ getCustomStyle: () => Record<string, string> | null;
16
+ }
17
+
18
+ export const useRedirectStore = create<RedirectState>()(
19
+ persist(
20
+ (set, get) => ({
21
+ decodedInfo: null,
22
+ isDecoded: false,
23
+ isDecodingError: false,
24
+ error: null,
25
+
26
+ setDecodedInfo: (info) =>
27
+ set({
28
+ decodedInfo: info,
29
+ isDecoded: true,
30
+ isDecodingError: false,
31
+ error: null,
32
+ }),
33
+
34
+ setDecodingError: (error) =>
35
+ set({
36
+ isDecodingError: true,
37
+ error,
38
+ }),
39
+
40
+ clearDecodedInfo: () =>
41
+ set({
42
+ decodedInfo: null,
43
+ isDecoded: false,
44
+ isDecodingError: false,
45
+ error: null,
46
+ }),
47
+
48
+ // Parse and return custom styles if available
49
+ getCustomStyle: () => {
50
+ const { decodedInfo } = get();
51
+
52
+ if (!decodedInfo?.customization?.fiu_custom_styles) {
53
+ return null;
54
+ }
55
+
56
+ try {
57
+ return JSON.parse(decodedInfo.customization.fiu_custom_styles);
58
+ } catch {
59
+ // console.error("Failed to parse custom styles:", error);
60
+ return null;
61
+ }
62
+ },
63
+ }),
64
+ {
65
+ name: "saafe-redirect-storage",
66
+ storage: createJSONStorage(() => sessionStorage),
67
+ partialize: (state) => ({
68
+ decodedInfo: state.decodedInfo,
69
+ isDecoded: state.isDecoded,
70
+ }),
71
+ }
72
+ )
73
+ );
@@ -0,0 +1,124 @@
1
+ import { create } from 'zustand'
2
+ import { createJSONStorage, persist } from 'zustand/middleware'
3
+
4
+ // Define step interface
5
+ interface StepItem {
6
+ step: number
7
+ title: string
8
+ path: string
9
+ parentStep?: number
10
+ }
11
+
12
+ interface StepState {
13
+ activeStep: number
14
+ completedSteps: number[]
15
+ totalSteps: number
16
+
17
+ // Actions
18
+ setActiveStep: (step: number) => void
19
+ setCompletedSteps: (steps: number[]) => void
20
+ markStepAsCompleted: (step: number) => void
21
+ determineActiveStep: (currentPath: string, steps: StepItem[]) => number
22
+ nextStep: () => void
23
+ previousStep: () => void
24
+ resetSteps: () => void
25
+ }
26
+
27
+ export const useStepStore = create<StepState>()(
28
+ persist(
29
+ set => ({
30
+ activeStep: 1,
31
+ completedSteps: [],
32
+ totalSteps: 1,
33
+
34
+ setActiveStep: (step: number) => set({ activeStep: step }),
35
+
36
+ setCompletedSteps: (steps: number[]) => set({ completedSteps: steps }),
37
+
38
+ markStepAsCompleted: (step: number) =>
39
+ set(state => ({
40
+ completedSteps: [...new Set([...state.completedSteps, step])]
41
+ })),
42
+
43
+ determineActiveStep: (currentPath: string, steps: StepItem[]) => {
44
+ // First check if we're on an exact path match
45
+ const exactPathMatch = steps.findIndex(
46
+ step => step.path === currentPath
47
+ )
48
+ if (exactPathMatch >= 0) return exactPathMatch + 1
49
+
50
+ // Check if we're on a path under link-accounts
51
+ if (currentPath.includes('/link-accounts/')) {
52
+ const pathSegment = currentPath.split('/').pop() || ''
53
+
54
+ // Flow paths are all part of the current category step
55
+ const flowPaths = ['discovery', 'link', 'discover-account']
56
+ if (flowPaths.includes(pathSegment)) {
57
+ return 2 // Default to Link Accounts if no category match
58
+ }
59
+
60
+ // Check if path matches a category directly - based on the URL path
61
+ const categorySegment = pathSegment.toLowerCase()
62
+ for (let i = 0; i < steps.length; i++) {
63
+ const step = steps[i]
64
+ // Skip non-child steps
65
+ if (!step.parentStep) continue
66
+
67
+ // Check if this step's title matches the path segment
68
+ if (step.title?.toLowerCase() === categorySegment) {
69
+ return i + 1
70
+ }
71
+ }
72
+
73
+ // Default to Link Accounts step
74
+ return 2
75
+ }
76
+
77
+ // We're not on a path in the steps
78
+ return 1 // Default to first step
79
+ },
80
+
81
+ nextStep: () =>
82
+ set(state => {
83
+ if (state.activeStep < state.totalSteps) {
84
+ const nextStepNumber = state.activeStep + 1
85
+ return {
86
+ activeStep: nextStepNumber,
87
+ completedSteps: [
88
+ ...new Set([...state.completedSteps, state.activeStep])
89
+ ]
90
+ }
91
+ }
92
+ return state
93
+ }),
94
+
95
+ previousStep: () =>
96
+ set(state => {
97
+ if (state.activeStep > 1) {
98
+ const prevStepNumber = state.activeStep - 1
99
+ return {
100
+ activeStep: prevStepNumber,
101
+ completedSteps: state.completedSteps.filter(
102
+ step => step !== state.activeStep - 1
103
+ )
104
+ }
105
+ }
106
+ return state
107
+ }),
108
+
109
+ resetSteps: () =>
110
+ set({
111
+ activeStep: 1,
112
+ completedSteps: []
113
+ })
114
+ }),
115
+ {
116
+ name: 'saafe-step-storage',
117
+ storage: createJSONStorage(() => sessionStorage),
118
+ partialize: state => ({
119
+ activeStep: state.activeStep,
120
+ completedSteps: state.completedSteps
121
+ })
122
+ }
123
+ )
124
+ )