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,396 @@
1
+ import { useEffect, useState } from 'react'
2
+ import BankCard from '@/components/cards/BankCard'
3
+ import OuterCard from '@/components/cards/OuterCard'
4
+ import SectionTitle from '@/components/title/SectionTitle'
5
+ import { AnimatedButton } from '@/components/ui/animatedButton'
6
+ import { Button } from '@/components/ui/button'
7
+ import { Checkbox } from '@/components/ui/checkbox'
8
+ import { MobileFooter } from '@/components/ui/mobile-footer'
9
+ import WebFooter from '@/components/ui/web-footer'
10
+ import { useFipStore } from '@/store/fip.store'
11
+ import { AlertCircle, PlusIcon } from 'lucide-react'
12
+ import { useNavigate } from 'react-router-dom'
13
+ import {
14
+ ProcessedDiscoveryResult,
15
+ useAccountDiscovery
16
+ } from '@/hooks/use-account-discovery'
17
+ import { Alert, AlertDescription } from '@/components/ui/alert'
18
+ import { useTranslation } from 'react-i18next'
19
+ import { Label } from '@/components/ui/label'
20
+ import { formatAccountNumber } from '@/utils/formatAccountNumber'
21
+ import { useSetPageTitle } from '@/hooks/use-page-title'
22
+ import DummyFooter from '@/components/dummyFooter'
23
+ import { useRedirectStore } from '@/store/redirect.store'
24
+ import { fiTypeCategoryMap } from '@/const/fiTypeCategoryMap'
25
+ interface AccountsToProceedProps {
26
+ state?: {
27
+ category: string
28
+ selectedFips: string[]
29
+ }
30
+ currentCategory?: string | null
31
+ }
32
+
33
+ const AccountsToProceed = ({
34
+ state,
35
+ currentCategory
36
+ }: AccountsToProceedProps) => {
37
+ const navigate = useNavigate()
38
+ const { t } = useTranslation()
39
+ const category = state?.category || currentCategory || 'BANKS'
40
+ const {
41
+ selectedFips,
42
+ groupedFips,
43
+ activeCategory,
44
+ setSelectedAccountToLink,
45
+ setSignature,
46
+ setOriginalAccounts,
47
+ originalAccounts
48
+ } = useFipStore()
49
+ const fipIds = state?.selectedFips || selectedFips[category] || []
50
+ useSetPageTitle(`Select accounts you want to link`)
51
+ const [discoveryResult, setDiscoveryResult] =
52
+ useState<ProcessedDiscoveryResult | null>(null)
53
+ const [selectedAccounts, setSelectedAccounts] = useState<string[]>([])
54
+ const [isLoading, setIsLoading] = useState(false)
55
+ const { decodedInfo } = useRedirectStore()
56
+
57
+ // Account discovery with enhanced error handling
58
+ const { mutate, errorMessage, clearError } = useAccountDiscovery()
59
+
60
+ // Format category name for display
61
+ const formatCategoryName = (name: string) => {
62
+ return name
63
+ .split('_')
64
+ .map(word => word.charAt(0) + word.slice(1).toLowerCase())
65
+ .join(' ')
66
+ }
67
+
68
+ const totalCategory = decodedInfo?.fipId?.split(',').map(fip => {
69
+ const fipData = activeCategory ? fiTypeCategoryMap[activeCategory]?.map((fiType: string) => {
70
+ const filteredFips = groupedFips[fiType]?.filter(f => f.id === fip);
71
+ return filteredFips?.[0]?.fiTypeList || [];
72
+ }).filter(Boolean) : [];
73
+ return fipData?.flat() || [];
74
+ }).flat() || [];
75
+
76
+ const requiredCategory = activeCategory ? fiTypeCategoryMap[activeCategory]?.filter((fiType: string) => decodedInfo?.fiTypesRequiredForConsent?.includes(fiType)) || [] : [];
77
+
78
+ // Discover accounts when component loads
79
+ useEffect(() => {
80
+ let mounted = true
81
+
82
+ if (fipIds.length > 0) {
83
+ setIsLoading(true)
84
+ clearError()
85
+
86
+ mutate(fipIds, {
87
+ onSuccess: result => {
88
+ if (!mounted) return
89
+ const groupedAccounts = result.accounts.reduce(
90
+ (acc: Record<string, typeof result.accounts>, account) => {
91
+ const key = account.type
92
+ if (!acc[key]) {
93
+ acc[key] = []
94
+ }
95
+ acc[key].push(account)
96
+ return acc
97
+ },
98
+ {}
99
+ )
100
+ setDiscoveryResult({ ...result, groupedAccounts })
101
+ setSignature(result.signature)
102
+ setOriginalAccounts(result.originalAccounts)
103
+
104
+ setIsLoading(false)
105
+ },
106
+ onError: () => {
107
+ if (!mounted) return
108
+ setIsLoading(false)
109
+ }
110
+ })
111
+ }
112
+
113
+ // Cleanup function to handle unmounting
114
+ return () => {
115
+ mounted = false
116
+ }
117
+ }, [fipIds]) // Only depend on fipIds changing
118
+
119
+ const handleProceed = () => {
120
+ const result: typeof originalAccounts = []
121
+ originalAccounts.forEach((item, index) => {
122
+ const data = item?.["DiscoveredAccounts"].filter(account => selectedAccounts.includes(account.accRefNumber))
123
+ if (data.length > 0) {
124
+ result.push({ ...item, DiscoveredAccounts: data, id: `${index} - ${item.fipName}` })
125
+ }
126
+ })
127
+
128
+ setSelectedAccountToLink(result)
129
+ // Navigate to account linking screen with selected accounts and signature
130
+ navigate('/link-accounts/link', {
131
+ state: {
132
+ category,
133
+ selectedFips: fipIds,
134
+ selectedAccounts,
135
+ signature: discoveryResult?.signature
136
+ }
137
+ })
138
+ }
139
+
140
+
141
+ const handleSelectAccount = (accountId: string) => {
142
+ setSelectedAccounts(prev => {
143
+ let updatedSelection;
144
+ if (prev.includes(accountId)) {
145
+ updatedSelection = prev.filter(id => id !== accountId);
146
+ } else {
147
+ updatedSelection = [...prev, accountId];
148
+ }
149
+ localStorage.setItem('selectedAccounts', JSON.stringify(updatedSelection));
150
+ return updatedSelection;
151
+ });
152
+ };
153
+
154
+
155
+
156
+ useEffect(() => {
157
+ const storedSelection = localStorage.getItem('selectedAccounts');
158
+ if (storedSelection) {
159
+ setSelectedAccounts(JSON.parse(storedSelection));
160
+ }
161
+ }, []);
162
+
163
+
164
+
165
+ const handleSelectAll = () => {
166
+ if (discoveryResult?.accounts) {
167
+ const allSelected = discoveryResult.accounts.map(account => account.id);
168
+
169
+ if (selectedAccounts.length === allSelected.length) {
170
+
171
+ setSelectedAccounts([]);
172
+ localStorage.setItem('selectedAccounts', JSON.stringify([]));
173
+ } else {
174
+
175
+ setSelectedAccounts(allSelected);
176
+ localStorage.setItem('selectedAccounts', JSON.stringify(allSelected));
177
+ }
178
+ }
179
+ };
180
+ useEffect(() => {
181
+ if (discoveryResult?.accounts) {
182
+ const allAccountIds = discoveryResult.accounts.map(account => account.id);
183
+ setSelectedAccounts(allAccountIds);
184
+ // Save to localStorage
185
+ localStorage.setItem('selectedAccounts', JSON.stringify(allAccountIds));
186
+ }
187
+ }, [discoveryResult]);
188
+
189
+
190
+ const retryDiscovery = () => {
191
+ if (fipIds.length > 0) {
192
+ setIsLoading(true)
193
+ clearError()
194
+
195
+ mutate(fipIds, {
196
+ onSuccess: result => {
197
+ setDiscoveryResult(result)
198
+
199
+ // Auto-select all accounts by default
200
+ if (result.accounts.length > 0) {
201
+ setSelectedAccounts(result.accounts.map(account => account.id))
202
+ }
203
+
204
+ setIsLoading(false)
205
+ },
206
+ onError: () => {
207
+ setIsLoading(false)
208
+ }
209
+ })
210
+ }
211
+ }
212
+
213
+ const renderAccountGroups = () => {
214
+ if (isLoading) {
215
+ return Array(3)
216
+ .fill(0)
217
+ .map((_, index) => (
218
+ <OuterCard key={`loading-${index}`} selected={false} loading={true}>
219
+ <BankCard bankName='' />
220
+ </OuterCard>
221
+ ))
222
+ }
223
+
224
+ if (
225
+ discoveryResult?.groupedAccounts &&
226
+ Object.keys(discoveryResult.groupedAccounts).length > 0
227
+ ) {
228
+ return Object.keys(discoveryResult.groupedAccounts).map(key => (
229
+ <>
230
+
231
+ <Label
232
+ className='text-xs md:text-md font-semibold text-consent-secondary mt-2'
233
+ key={key}
234
+
235
+ >
236
+ {key}
237
+ </Label>
238
+ {discoveryResult.groupedAccounts[key].map(account => (
239
+ <OuterCard
240
+ key={account.id}
241
+ selected={selectedAccounts.includes(account.id)}
242
+ onSelect={() => handleSelectAccount(account.id)}
243
+ >
244
+ <BankCard
245
+ loading={false}
246
+ badgeText={account.isNew ? 'New' : undefined}
247
+ subText={`${account.type || 'Account'} | ${formatAccountNumber(account.maskedAccountNumber) || '**XXXX'
248
+ }`}
249
+ bankName={account.bankName || 'Financial Institution'}
250
+ image={account.logoUrl || undefined}
251
+ rightSection={
252
+ <Checkbox
253
+ checked={selectedAccounts.includes(account.id)}
254
+ onCheckedChange={() => handleSelectAccount(account.id)}
255
+ />
256
+ }
257
+ />
258
+ </OuterCard>
259
+ ))}
260
+ </>
261
+ ))
262
+ }
263
+
264
+ return (
265
+ <div className='text-center p-4 text-gray-500'>
266
+ {errorMessage
267
+ ? 'Failed to discover accounts'
268
+ : 'No accounts found for the selected banks'}
269
+ </div>
270
+ )
271
+ }
272
+
273
+ return (
274
+ <div className='h-full flex flex-col gap-1'>
275
+ <div className='flex flex-col gap-1 w-full md:px-14 md:mt-10 sm:px-4'>
276
+
277
+ <SectionTitle
278
+ className='mt-4'
279
+ title={`${t(
280
+ 'categories.' +
281
+ formatCategoryName(category).replace(' ', '_').toLowerCase()
282
+ )} ${discoveryResult?.accounts?.length === 1 ? t('keywords.account') : t('keywords.accounts')}`}
283
+
284
+ rightSection={
285
+ <AnimatedButton
286
+ variant={'ghost'}
287
+ onClick={handleSelectAll}
288
+ disabled={
289
+ !discoveryResult?.accounts ||
290
+ discoveryResult.accounts.length === 0
291
+ }
292
+ >
293
+ <p className='text-primary'>
294
+ {discoveryResult?.accounts &&
295
+ selectedAccounts.length === discoveryResult.accounts.length &&
296
+ discoveryResult.accounts.length > 0
297
+ ? t('unselectAll')
298
+ : t('selectAll')}
299
+ </p>
300
+ </AnimatedButton>
301
+ }
302
+ />
303
+
304
+ {errorMessage && (
305
+ <Alert variant='destructive' className='flex items-center'>
306
+ <AlertCircle className='w-4 h-4' />
307
+ <AlertDescription className='flex items-center justify-between'>
308
+ {errorMessage}
309
+ <Button
310
+ variant='outline'
311
+ size='sm'
312
+ onClick={retryDiscovery}
313
+ className='ml-2'
314
+ >
315
+ Retry
316
+ </Button>
317
+ </AlertDescription>
318
+ </Alert>
319
+ )}
320
+
321
+ <div className='flex flex-col gap-2'>
322
+ <div className='max-h-[60vh] overflow-y-auto'>
323
+ <div className='flex flex-col gap-2 mb-4'>
324
+ {renderAccountGroups()}
325
+ </div>
326
+ </div>
327
+ {requiredCategory?.every(fiType => totalCategory?.includes(fiType)) ? null :
328
+ <Button
329
+ variant={'outline'}
330
+ className='bg-primary-foreground text-primary border-primary hover:bg-primary/10 h-[50px] dark:bg-ring'
331
+ onClick={() =>
332
+ navigate(`/link-accounts/${category.toLowerCase()}`, {
333
+ state: {
334
+ category,
335
+ selectedFips: fipIds
336
+ }
337
+ })
338
+ }
339
+ size={'lg'}
340
+ >
341
+ <div className='flex items-center gap-2 text-md text-primary font-[600] dark:text-muted-secondary'>
342
+ <PlusIcon
343
+ strokeWidth='2.3'
344
+ className='size-5 text-primary dark:text-muted-secondary'
345
+ />{' '}
346
+ {t('discoverMore')}
347
+ </div>
348
+ </Button>
349
+ }
350
+ </div>
351
+ <DummyFooter />
352
+ </div>
353
+
354
+ <MobileFooter show={selectedAccounts.length > 0}>
355
+ <div className='flex flex-col items-center w-full gap-4'>
356
+ <AnimatedButton
357
+ onClick={handleProceed}
358
+ size='lg'
359
+ className='w-full h-[50px]'
360
+ disabled={selectedAccounts.length === 0}
361
+ >
362
+ {t('proceed')}
363
+ </AnimatedButton>
364
+ <AnimatedButton
365
+ onClick={() => { setSelectedAccountToLink([]); setSelectedAccounts([]) }}
366
+ variant={'ghost'}
367
+ size='lg'
368
+ className='text-primary'
369
+ >
370
+ {t('cancel')}
371
+ </AnimatedButton>
372
+ </div>
373
+ </MobileFooter>
374
+
375
+ <WebFooter show={selectedAccounts.length > 0}>
376
+ <AnimatedButton
377
+ onClick={() => { setSelectedAccountToLink([]); setSelectedAccounts([]) }}
378
+ variant={'ghost'}
379
+ size='lg'
380
+ className='text-primary'
381
+ >
382
+ {t('cancel')}
383
+ </AnimatedButton>
384
+ <AnimatedButton
385
+ onClick={handleProceed}
386
+ size='lg'
387
+ disabled={selectedAccounts.length === 0}
388
+ >
389
+ {t('proceed')}
390
+ </AnimatedButton>
391
+ </WebFooter>
392
+ </div>
393
+ )
394
+ }
395
+
396
+ export default AccountsToProceed
@@ -0,0 +1,76 @@
1
+ "use client";
2
+
3
+ import { FrostedLayout } from "@/components/layouts/FrostedLayout"
4
+ import LinkAccounts from "./link-accounts";
5
+ import { useLocation, useParams } from "react-router-dom";
6
+ import AccountsToProceed from "./AccountsToProceed";
7
+ import LinkSelectedAccounts from "./LinkSelectedAccounts";
8
+ import DiscoverAccount from "./DiscoverAccount";
9
+ import { useFipStore } from "@/store/fip.store";
10
+ import { useEffect } from "react";
11
+
12
+ const Discover = () => {
13
+ const { category } = useParams();
14
+ const location = useLocation();
15
+ const path = location.pathname.split('/').pop() || '';
16
+ const { activeCategory, setActiveCategory, categories } = useFipStore();
17
+
18
+ // For step navigation in the sidebar - detect category from path
19
+ useEffect(() => {
20
+ // Known flow paths that aren't categories
21
+ const flowPaths = ['discovery', 'link', 'discover-account'];
22
+ const isFlowPath = flowPaths.includes(path);
23
+
24
+ if (!isFlowPath && category) {
25
+ // We're on a category-specific path like /link-accounts/banks
26
+ const matchingCategory = categories.find(cat =>
27
+ cat.toLowerCase() === category.toLowerCase()
28
+ );
29
+
30
+ if (matchingCategory) {
31
+ setActiveCategory(matchingCategory);
32
+ }
33
+ }
34
+ else if (location.state?.activeCategory) {
35
+ // When returning with a specific category in state
36
+ setActiveCategory(location.state.activeCategory);
37
+ }
38
+ }, [path, category, categories, setActiveCategory, location.state]);
39
+
40
+ const getCurrentStep = (newPath: string) => {
41
+ // Flow paths
42
+ switch (newPath || path) {
43
+ case 'discovery':
44
+ case 'proceed':
45
+ return <AccountsToProceed
46
+ state={location.state as { category: string; selectedFips: string[] }}
47
+ currentCategory={activeCategory}
48
+ />;
49
+
50
+ case 'link':
51
+ return <LinkSelectedAccounts
52
+ state={location.state as { category: string; selectedFips: string[] }}
53
+ currentCategory={activeCategory}
54
+ />;
55
+
56
+ case 'discover-account':
57
+ return <DiscoverAccount
58
+ state={location.state as { category: string; selectedFips?: string[] }}
59
+ currentCategory={activeCategory}
60
+ />;
61
+
62
+ default:
63
+ // If we're on a category path or any other path, show FIP listing
64
+ // This handles all category paths like /link-accounts/banks, /link-accounts/investment etc.
65
+ return <LinkAccounts />;
66
+ }
67
+ }
68
+
69
+ return (
70
+ <FrostedLayout>
71
+ {getCurrentStep()}
72
+ </FrostedLayout>
73
+ )
74
+ }
75
+
76
+ export default Discover