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,913 @@
|
|
1
|
+
import React, { useState, useEffect, useMemo } from 'react'
|
2
|
+
import { useNavigate, useLocation } from 'react-router-dom'
|
3
|
+
import SectionTitle from '@/components/title/SectionTitle'
|
4
|
+
import { PlusIcon, Search, AlertCircle, InfoIcon } from 'lucide-react'
|
5
|
+
import OuterCard from '@/components/cards/OuterCard'
|
6
|
+
import BankCard from '@/components/cards/BankCard'
|
7
|
+
import { Checkbox } from '@/components/ui/checkbox'
|
8
|
+
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
9
|
+
import { Input } from '@/components/ui/input'
|
10
|
+
import { AnimatedButton } from '@/components/ui/animatedButton'
|
11
|
+
import Modal from '@/components/ui/modal'
|
12
|
+
import { OTPInputComponent } from '@/components/ui/otp-input'
|
13
|
+
import { useMediaQuery } from '@/hooks/use-media-query'
|
14
|
+
import { BottomSheet } from '@/components/ui/bottom-sheet'
|
15
|
+
import { MobileFooter } from '@/components/ui/mobile-footer'
|
16
|
+
import WebFooter from '@/components/ui/web-footer'
|
17
|
+
import { useFipQuery } from '@/hooks/use-fip-query'
|
18
|
+
import { useFipStore } from '@/store/fip.store'
|
19
|
+
import { useAuthStore } from '@/store/auth.store'
|
20
|
+
import { Skeleton } from '@/components/ui/skeleton'
|
21
|
+
import { useMutation, useQuery } from '@tanstack/react-query'
|
22
|
+
import { accountService } from '@/services/api'
|
23
|
+
import { useRedirectStore } from '@/store/redirect.store'
|
24
|
+
import { useTranslation } from 'react-i18next'
|
25
|
+
import { Label } from '@/components/ui/label'
|
26
|
+
import { useSetPageTitle } from '@/hooks/use-page-title'
|
27
|
+
import DummyFooter from '@/components/dummyFooter'
|
28
|
+
import { EVENTS, trackEvent } from '@/utils/posthog'
|
29
|
+
import { fiTypeCategoryMap } from '@/const/fiTypeCategoryMap'
|
30
|
+
// Helper functions
|
31
|
+
const formatCategoryName = (category: string) => {
|
32
|
+
return category
|
33
|
+
.split('_')
|
34
|
+
.map(word => word.charAt(0) + word.slice(1).toLowerCase())
|
35
|
+
.join(' ')
|
36
|
+
}
|
37
|
+
|
38
|
+
function debounce(func, delay) {
|
39
|
+
let timeoutId;
|
40
|
+
|
41
|
+
return function (...args) {
|
42
|
+
clearTimeout(timeoutId);
|
43
|
+
timeoutId = setTimeout(() => {
|
44
|
+
func.apply(this, args);
|
45
|
+
}, delay);
|
46
|
+
};
|
47
|
+
}
|
48
|
+
|
49
|
+
const AddNumberModalContent = ({ showModal, setShowModal, t, addNewNoQuery, handleAddNumber }: {
|
50
|
+
showModal: { number: string; error: string };
|
51
|
+
setShowModal: React.Dispatch<React.SetStateAction<{
|
52
|
+
isOpen: boolean;
|
53
|
+
title: string;
|
54
|
+
content: React.ReactNode | null;
|
55
|
+
number: string;
|
56
|
+
otp: string | null;
|
57
|
+
error: string;
|
58
|
+
}>>;
|
59
|
+
t: (key: string) => string;
|
60
|
+
addNewNoQuery: {
|
61
|
+
mutate: (data: undefined, options: {
|
62
|
+
onSuccess: () => void;
|
63
|
+
onError: (error: Error) => void;
|
64
|
+
}) => void;
|
65
|
+
};
|
66
|
+
handleAddNumber: (number: string) => void;
|
67
|
+
}) => {
|
68
|
+
const [inputValue, setInputValue] = useState({ number: showModal.number, error: showModal.error });
|
69
|
+
|
70
|
+
useEffect(() => {
|
71
|
+
setInputValue({ number: showModal.number, error: showModal.error });
|
72
|
+
}, [showModal.number]);
|
73
|
+
return useMemo(() => (
|
74
|
+
<div className='flex flex-col items-center gap-5'>
|
75
|
+
<p className='text-base font-normal dark:text-muted-secondary'>
|
76
|
+
{t('pleaseEnterTheNewNumberYouWishToLinkBelow')}
|
77
|
+
</p>
|
78
|
+
<Input
|
79
|
+
type="tel"
|
80
|
+
inputMode="numeric"
|
81
|
+
className='h-12'
|
82
|
+
placeholder={t('mobileNoPlaceholder')}
|
83
|
+
maxLength={10}
|
84
|
+
value={inputValue.number}
|
85
|
+
onChange={e => {
|
86
|
+
const newValue = e.target.value;
|
87
|
+
const numericValue = newValue.replace(/[^0-9]/g, '');
|
88
|
+
setInputValue({ number: numericValue, error: '' });
|
89
|
+
setShowModal(prev => ({
|
90
|
+
...prev,
|
91
|
+
number: numericValue,
|
92
|
+
error: numericValue.length === 10 ? '' : 'Please enter a valid 10-digit mobile number'
|
93
|
+
}));
|
94
|
+
}}
|
95
|
+
/>
|
96
|
+
{inputValue.error && (
|
97
|
+
<p className="text-red-500 text-sm mt-[-20px]">{inputValue.error}</p>
|
98
|
+
)}
|
99
|
+
<AnimatedButton
|
100
|
+
className='w-full h-11 mt-2'
|
101
|
+
onClick={() => {
|
102
|
+
if (inputValue.number.length !== 10) {
|
103
|
+
setInputValue({ number: inputValue.number, error: 'Please enter a valid 10-digit mobile number' });
|
104
|
+
setShowModal(prev => ({ ...prev, error: 'Please enter a valid 10-digit mobile number' }));
|
105
|
+
return;
|
106
|
+
}
|
107
|
+
addNewNoQuery.mutate(undefined, {
|
108
|
+
onSuccess: () => {
|
109
|
+
handleAddNumber(inputValue.number)
|
110
|
+
},
|
111
|
+
onError: (error: Error) => {
|
112
|
+
// console.error(error)
|
113
|
+
setInputValue({ number: inputValue.number, error: error.response?.data?.errorCode || 'Something went wrong' });
|
114
|
+
}
|
115
|
+
})
|
116
|
+
}}
|
117
|
+
>
|
118
|
+
{t('login.getOtp')}
|
119
|
+
</AnimatedButton>
|
120
|
+
</div>
|
121
|
+
), [inputValue, showModal.error, t, setShowModal, addNewNoQuery, handleAddNumber])
|
122
|
+
}
|
123
|
+
|
124
|
+
const LinkAccounts = () => {
|
125
|
+
const { t } = useTranslation()
|
126
|
+
useSetPageTitle(t('selectAndDiscoverYourAccounts'))
|
127
|
+
const navigate = useNavigate()
|
128
|
+
const location = useLocation()
|
129
|
+
const { decodedInfo } = useRedirectStore()
|
130
|
+
const [searchQuery, setSearchQuery] = useState('')
|
131
|
+
const [showModal, setShowModal] = useState<{
|
132
|
+
isOpen: boolean;
|
133
|
+
title: string;
|
134
|
+
content: React.ReactNode | null;
|
135
|
+
number: string;
|
136
|
+
otp: string | null;
|
137
|
+
error: string;
|
138
|
+
}>({
|
139
|
+
isOpen: false,
|
140
|
+
title: t('addNewMobileNumber'),
|
141
|
+
content: null,
|
142
|
+
number: '',
|
143
|
+
otp: null,
|
144
|
+
error: ''
|
145
|
+
})
|
146
|
+
const [showCloseModal, setShowCloseModal] = useState({ isOpen: false })
|
147
|
+
|
148
|
+
// Extract consent handle from query params or state
|
149
|
+
const getConsentHandle = () => {
|
150
|
+
return decodedInfo?.srcref
|
151
|
+
}
|
152
|
+
|
153
|
+
const consentHandle = getConsentHandle()
|
154
|
+
|
155
|
+
// Detect mobile screens
|
156
|
+
const isMobile = useMediaQuery('(max-width: 768px)')
|
157
|
+
|
158
|
+
// Get user's selected mobile
|
159
|
+
const { user } = useAuthStore()
|
160
|
+
const mobileNumber = user?.phoneNumber
|
161
|
+
|
162
|
+
// FIP data from store
|
163
|
+
const {
|
164
|
+
fips,
|
165
|
+
groupedFips,
|
166
|
+
selectedFips,
|
167
|
+
activeCategory,
|
168
|
+
categories,
|
169
|
+
selectedMobileNumber,
|
170
|
+
accountsToNotify,
|
171
|
+
isLoading: storeLoading,
|
172
|
+
error: storeError,
|
173
|
+
setSelectedMobileNumber,
|
174
|
+
setActiveCategory,
|
175
|
+
addSelectedFip,
|
176
|
+
removeSelectedFip,
|
177
|
+
setSelectedFips,
|
178
|
+
clearSelections,
|
179
|
+
setIdentifiers,
|
180
|
+
setAccountsToNotify
|
181
|
+
} = useFipStore()
|
182
|
+
|
183
|
+
// Fetch FIP data with React Query - use dynamic consent handle
|
184
|
+
const {
|
185
|
+
refetch,
|
186
|
+
isLoading: queryLoading,
|
187
|
+
error: queryError
|
188
|
+
} = useFipQuery(consentHandle)
|
189
|
+
|
190
|
+
// Add state to track when we should navigate
|
191
|
+
const [shouldNavigate, setShouldNavigate] = useState(false);
|
192
|
+
|
193
|
+
const totalCategory = decodedInfo?.fipId?.split(',').map(fip => {
|
194
|
+
const fipData = fiTypeCategoryMap[activeCategory]?.map((fiType) => {
|
195
|
+
const filteredFips = groupedFips[fiType]?.filter(f => f.id === fip);
|
196
|
+
return filteredFips?.[0]?.fiTypeList || [];
|
197
|
+
}).filter(Boolean);
|
198
|
+
return fipData?.flat() || [];
|
199
|
+
}).flat() || [];
|
200
|
+
|
201
|
+
const requiredCategory = fiTypeCategoryMap[activeCategory]?.filter(fiType => decodedInfo?.fiTypesRequiredForConsent?.includes(fiType)) || [];
|
202
|
+
|
203
|
+
// Effect to handle setting selected FIPs
|
204
|
+
useEffect(() => {
|
205
|
+
if (!queryLoading && !queryError && decodedInfo?.fipId && activeCategory && requiredCategory.every(fiType => totalCategory?.includes(fiType))) {
|
206
|
+
const fipIds = decodedInfo.fipId.split(',');
|
207
|
+
setSelectedFips(activeCategory, fipIds);
|
208
|
+
setShouldNavigate(true);
|
209
|
+
}
|
210
|
+
}, [queryLoading, queryError, decodedInfo?.fipId, activeCategory]);
|
211
|
+
|
212
|
+
// Effect to handle navigation
|
213
|
+
useEffect(() => {
|
214
|
+
if (shouldNavigate && selectedFips[activeCategory]?.length > 0) {
|
215
|
+
handleNext();
|
216
|
+
setShouldNavigate(false);
|
217
|
+
}
|
218
|
+
}, [shouldNavigate]);
|
219
|
+
|
220
|
+
const getMobileNumbersQuery = useQuery({
|
221
|
+
queryKey: ['mobile-numbers'],
|
222
|
+
queryFn: async () => accountService.getMobileNumbers()
|
223
|
+
})
|
224
|
+
|
225
|
+
const getMostUsedBanksQuery = useQuery({
|
226
|
+
queryKey: ['most-used-banks'],
|
227
|
+
queryFn: async () => accountService.getMostUsedBanks({ type: 'DEPOSIT' as string }),
|
228
|
+
enabled: !!activeCategory
|
229
|
+
})
|
230
|
+
|
231
|
+
const addNewNoQuery = useMutation({
|
232
|
+
mutationFn: async () =>
|
233
|
+
accountService.addNewNumber({
|
234
|
+
value: showModal?.number || '',
|
235
|
+
type: 'MOBILE',
|
236
|
+
categoryType: 'STRONG'
|
237
|
+
})
|
238
|
+
})
|
239
|
+
|
240
|
+
const verifyNewNoQuery = useMutation({
|
241
|
+
mutationFn: async () =>
|
242
|
+
accountService.verifyNewNumber({
|
243
|
+
phoneNumber: showModal?.number || '',
|
244
|
+
code: showModal?.otp || '',
|
245
|
+
otpUniqueID: addNewNoQuery?.data?.mobile?.otpUniqueID || '',
|
246
|
+
identifierType: 'MOBILE'
|
247
|
+
}),
|
248
|
+
onSuccess: () => {
|
249
|
+
getMobileNumbersQuery.refetch()
|
250
|
+
setShowModal({
|
251
|
+
isOpen: false,
|
252
|
+
title: t('addNewMobileNumber'),
|
253
|
+
content: null,
|
254
|
+
number: '',
|
255
|
+
otp: null,
|
256
|
+
error: ''
|
257
|
+
})
|
258
|
+
},
|
259
|
+
onError: error => {
|
260
|
+
setShowModal(old => ({ ...old, error: error.response?.data?.errorCode || 'Something went wrong' }))
|
261
|
+
// console.error(error)
|
262
|
+
}
|
263
|
+
})
|
264
|
+
|
265
|
+
// Combine loading and error states
|
266
|
+
const isLoading = storeLoading || queryLoading
|
267
|
+
const error = storeError || (queryError as Error)?.message
|
268
|
+
|
269
|
+
// Set selected mobile number
|
270
|
+
useEffect(() => {
|
271
|
+
if (mobileNumber) {
|
272
|
+
setSelectedMobileNumber(mobileNumber)
|
273
|
+
}
|
274
|
+
}, [mobileNumber, setSelectedMobileNumber])
|
275
|
+
|
276
|
+
useEffect(() => {
|
277
|
+
if (searchQuery.length) {
|
278
|
+
debounce(trackEvent(EVENTS.SEARCH_BANKS, { searchValue: searchQuery }), 300)
|
279
|
+
}
|
280
|
+
}, [searchQuery])
|
281
|
+
|
282
|
+
// Determine the active category from URL if not already set
|
283
|
+
useEffect(() => {
|
284
|
+
const path = location.pathname
|
285
|
+
const categoryFromPath = path.split('/').pop()
|
286
|
+
|
287
|
+
// If the path includes a category name and it matches one of our categories
|
288
|
+
if (categoryFromPath) {
|
289
|
+
const matchingCategory = categories.find(
|
290
|
+
cat => cat.toLowerCase() === categoryFromPath.toLowerCase()
|
291
|
+
)
|
292
|
+
|
293
|
+
if (matchingCategory && matchingCategory !== activeCategory) {
|
294
|
+
setActiveCategory(matchingCategory)
|
295
|
+
}
|
296
|
+
} else if (categories.length > 0 && !activeCategory) {
|
297
|
+
// Set first category as active if none is active and redirect
|
298
|
+
const firstCategory = categories[0]
|
299
|
+
setActiveCategory(firstCategory)
|
300
|
+
navigate(`/link-accounts/${firstCategory.toLowerCase()}`, {
|
301
|
+
replace: true
|
302
|
+
})
|
303
|
+
}
|
304
|
+
}, [
|
305
|
+
location.pathname,
|
306
|
+
categories,
|
307
|
+
activeCategory,
|
308
|
+
setActiveCategory,
|
309
|
+
navigate
|
310
|
+
])
|
311
|
+
|
312
|
+
// Show bottom sheet only when there are selected FIPs in the active category
|
313
|
+
const hasSelectedFips =
|
314
|
+
activeCategory && selectedFips[activeCategory]?.length > 0
|
315
|
+
const showBottomSheet = hasSelectedFips
|
316
|
+
|
317
|
+
// Get FIPs for the active category
|
318
|
+
const getActiveCategoryFips = () => {
|
319
|
+
if (!activeCategory) return []
|
320
|
+
|
321
|
+
// Create a Map to store unique FIPs by their ID
|
322
|
+
const uniqueFipsMap = new Map<string, typeof fips[0]>()
|
323
|
+
|
324
|
+
// Get FIP types for the active category
|
325
|
+
const fipTypes = fiTypeCategoryMap[activeCategory] || []
|
326
|
+
|
327
|
+
// Add FIPs to the Map using ID as key
|
328
|
+
fipTypes.forEach(fipType => {
|
329
|
+
const categoryFips = groupedFips[fipType] || []
|
330
|
+
categoryFips.forEach(fip => {
|
331
|
+
if (fip.name && (!searchQuery || fip.name.toLowerCase().includes(searchQuery.toLowerCase()))) {
|
332
|
+
// Only add if we haven't seen this ID before
|
333
|
+
if (!uniqueFipsMap.has(fip.id)) {
|
334
|
+
uniqueFipsMap.set(fip.id, fip)
|
335
|
+
}
|
336
|
+
}
|
337
|
+
})
|
338
|
+
})
|
339
|
+
|
340
|
+
// Convert Map values to array and sort
|
341
|
+
const sortedFips = Array.from(uniqueFipsMap.values()).sort((a, b) => {
|
342
|
+
if (a.id < b.id) return -1
|
343
|
+
if (a.id > b.id) return 1
|
344
|
+
return 0
|
345
|
+
})
|
346
|
+
|
347
|
+
// Return enabled FIPs first, then disabled ones
|
348
|
+
return [
|
349
|
+
...sortedFips.filter(fip => fip.isEnabled),
|
350
|
+
...sortedFips.filter(fip => !fip.isEnabled)
|
351
|
+
]
|
352
|
+
}
|
353
|
+
|
354
|
+
const handleNext = () => {
|
355
|
+
if (!activeCategory) return
|
356
|
+
|
357
|
+
// Only proceed if user has selected providers
|
358
|
+
console.log(" scroll ")
|
359
|
+
if (selectedFips[activeCategory]?.length > 0) {
|
360
|
+
let redirect = false
|
361
|
+
const identifiersList: any[] = []
|
362
|
+
fips
|
363
|
+
.filter(fip =>
|
364
|
+
selectedFips?.[activeCategory as string]?.includes(fip.id as string)
|
365
|
+
)
|
366
|
+
.forEach(fip => {
|
367
|
+
fip.Identifiers?.forEach(identifier => {
|
368
|
+
if (!identifiersList.find(i => i.type === identifier.type)) {
|
369
|
+
const value =
|
370
|
+
identifier.type == 'PAN'
|
371
|
+
? decodedInfo?.pan
|
372
|
+
: identifier.type == 'AADHAAR'
|
373
|
+
? decodedInfo?.aadhaar
|
374
|
+
: identifier.type == 'MOBILE'
|
375
|
+
? selectedMobileNumber
|
376
|
+
: null
|
377
|
+
identifiersList.push({ ...identifier, value })
|
378
|
+
}
|
379
|
+
})
|
380
|
+
})
|
381
|
+
|
382
|
+
setIdentifiers(identifiersList)
|
383
|
+
if (identifiersList?.length > 0 && Boolean(identifiersList.find(val => !val.value)?.type)) {
|
384
|
+
if (identifiersList.find(i => i.type === 'PAN')) {
|
385
|
+
redirect = true
|
386
|
+
}
|
387
|
+
if (identifiersList.find(i => i.type === 'AADHAAR')) {
|
388
|
+
redirect = true
|
389
|
+
}
|
390
|
+
}
|
391
|
+
|
392
|
+
if (redirect) {
|
393
|
+
navigate(`/link-accounts/discover-account`, {
|
394
|
+
state: {
|
395
|
+
category: activeCategory,
|
396
|
+
selectedFips: selectedFips[activeCategory]
|
397
|
+
}
|
398
|
+
})
|
399
|
+
return null
|
400
|
+
}
|
401
|
+
// Navigate to the account discovery screen with category info
|
402
|
+
navigate(`/link-accounts/discovery`, {
|
403
|
+
state: {
|
404
|
+
category: activeCategory,
|
405
|
+
selectedFips: selectedFips[activeCategory]
|
406
|
+
}
|
407
|
+
})
|
408
|
+
} else {
|
409
|
+
// Show some notification that they need to select providers first
|
410
|
+
// console.warn('Please select at least one provider')
|
411
|
+
}
|
412
|
+
}
|
413
|
+
|
414
|
+
const handleAddNumber = (value: string) => {
|
415
|
+
setShowModal(old => ({
|
416
|
+
...old,
|
417
|
+
isOpen: true,
|
418
|
+
title: t('verifyNewMobileNumber'),
|
419
|
+
content: 'otp',
|
420
|
+
otp: null,
|
421
|
+
error: ''
|
422
|
+
}))
|
423
|
+
}
|
424
|
+
|
425
|
+
const handleTabClose = () => {
|
426
|
+
window.close()
|
427
|
+
}
|
428
|
+
|
429
|
+
const openAddNumberModal = (currentNumber: string) => {
|
430
|
+
setShowModal(prev => ({
|
431
|
+
...prev,
|
432
|
+
isOpen: true,
|
433
|
+
title: t('addNewMobileNumber'),
|
434
|
+
content: <AddNumberModalContent
|
435
|
+
showModal={{ ...showModal, number: currentNumber ? currentNumber : showModal.number }}
|
436
|
+
setShowModal={setShowModal}
|
437
|
+
t={t}
|
438
|
+
addNewNoQuery={addNewNoQuery}
|
439
|
+
handleAddNumber={(number: string) => handleAddNumber(number)}
|
440
|
+
/>,
|
441
|
+
otp: null,
|
442
|
+
error: ''
|
443
|
+
}))
|
444
|
+
}
|
445
|
+
|
446
|
+
const toggleFipSelection = (fipId: string) => {
|
447
|
+
if (!activeCategory) return
|
448
|
+
|
449
|
+
if (selectedFips[activeCategory]?.includes(fipId)) {
|
450
|
+
removeSelectedFip(activeCategory, fipId)
|
451
|
+
} else {
|
452
|
+
addSelectedFip(activeCategory, fipId)
|
453
|
+
}
|
454
|
+
}
|
455
|
+
|
456
|
+
// Show skeleton loaders during loading
|
457
|
+
if ((decodedInfo?.fipId || (queryLoading && shouldNavigate)) && requiredCategory.every(fiType => totalCategory?.includes(fiType))) {
|
458
|
+
return (
|
459
|
+
<div className='flex flex-col gap-4 pb-16 md:pb-16 md:p-16 sm:p-4'>
|
460
|
+
{Array(3)
|
461
|
+
.fill(0)
|
462
|
+
.map((_, index) => (
|
463
|
+
<OuterCard key={`loading-${index}`} selected={false} loading={true}>
|
464
|
+
<BankCard bankName='' />
|
465
|
+
</OuterCard>
|
466
|
+
))}
|
467
|
+
</div>
|
468
|
+
)
|
469
|
+
}
|
470
|
+
if (isLoading) {
|
471
|
+
return (
|
472
|
+
<div className='flex flex-col gap-4 pb-16 md:pb-16 md:p-16 sm:p-4'>
|
473
|
+
<Skeleton className='h-8 w-72 mb-4' />
|
474
|
+
<div className='grid grid-cols-1 md:grid-cols-3 gap-4'>
|
475
|
+
<Skeleton className='h-16 w-full' />
|
476
|
+
<Skeleton className='h-16 w-full' />
|
477
|
+
<Skeleton className='h-16 w-full' />
|
478
|
+
</div>
|
479
|
+
<Skeleton className='h-8 w-72 mt-8 mb-4' />
|
480
|
+
<Skeleton className='h-12 w-full mb-4' />
|
481
|
+
<Skeleton className='h-4 w-72 mt-0 mb-0' />
|
482
|
+
<div className='grid grid-cols-1 md:grid-cols-3 gap-4'>
|
483
|
+
{[...Array(6)].map((_, i) => (
|
484
|
+
<Skeleton key={i} className='h-16 w-full' />
|
485
|
+
))}
|
486
|
+
</div>
|
487
|
+
<Skeleton className='h-4 w-72 mt-4 mb-0' />
|
488
|
+
<div className='flex flex-col gap-4'>
|
489
|
+
{[...Array(6)].map((_, i) => (
|
490
|
+
<Skeleton key={i} className='h-16 w-full' />
|
491
|
+
))}
|
492
|
+
</div>
|
493
|
+
</div>
|
494
|
+
)
|
495
|
+
}
|
496
|
+
|
497
|
+
// Show error message if there was an error
|
498
|
+
if (error) {
|
499
|
+
return (
|
500
|
+
<div className='flex flex-col items-center justify-center p-8 h-64'>
|
501
|
+
<div className='flex items-center gap-2 text-red-500 mb-4'>
|
502
|
+
<AlertCircle size={20} />
|
503
|
+
<p>{error}</p>
|
504
|
+
</div>
|
505
|
+
<AnimatedButton onClick={() => refetch()}>Retry</AnimatedButton>
|
506
|
+
</div>
|
507
|
+
)
|
508
|
+
}
|
509
|
+
|
510
|
+
// No categories or no FIPs
|
511
|
+
if (!categories.length || !Object.keys(groupedFips).length) {
|
512
|
+
return (
|
513
|
+
<div className='flex flex-col items-center justify-center p-8 h-64'>
|
514
|
+
<div className='flex items-center gap-2 text-amber-500 mb-4'>
|
515
|
+
<AlertCircle size={20} />
|
516
|
+
<p>No financial information providers available</p>
|
517
|
+
</div>
|
518
|
+
<AnimatedButton onClick={() => refetch()}>Refresh</AnimatedButton>
|
519
|
+
</div>
|
520
|
+
)
|
521
|
+
}
|
522
|
+
|
523
|
+
const activeCategoryFips = getActiveCategoryFips()
|
524
|
+
|
525
|
+
// Render different content based on the active category
|
526
|
+
const renderCategoryContent = () => {
|
527
|
+
if (!activeCategory) return null
|
528
|
+
|
529
|
+
return (
|
530
|
+
<>
|
531
|
+
|
532
|
+
<SectionTitle
|
533
|
+
className='mt-8 mb-2'
|
534
|
+
title={
|
535
|
+
t('keywords.select').charAt(0).toUpperCase() + t('keywords.select').slice(1)
|
536
|
+
+
|
537
|
+
' ' +
|
538
|
+
t(`categories.${activeCategory.toLowerCase()}`) +
|
539
|
+
' to ' +
|
540
|
+
t('keywords.discover')
|
541
|
+
// +
|
542
|
+
// ' ' +
|
543
|
+
// t('keywords.your') +
|
544
|
+
// ' ' +
|
545
|
+
// t('keywords.accounts')
|
546
|
+
}
|
547
|
+
/>
|
548
|
+
<Input
|
549
|
+
type='text'
|
550
|
+
placeholder={t('Search')}
|
551
|
+
value={searchQuery}
|
552
|
+
onChange={e => setSearchQuery(e.target.value)}
|
553
|
+
leftSection={
|
554
|
+
<Search
|
555
|
+
className='h-[18px] w-[18px] text-consent-secondary'
|
556
|
+
name='search'
|
557
|
+
/>
|
558
|
+
}
|
559
|
+
className='h-12 focus-visible:border-primary'
|
560
|
+
/>
|
561
|
+
|
562
|
+
{activeCategory === 'BANK' && searchQuery?.trim().length === 0 && <div className='flex flex-col gap-2 mt-6'>
|
563
|
+
{getMostUsedBanksQuery?.data?.['BANK']?.length > 0 && <Label className='text-sm md:text-md font-medium text-consent-secondary'>Most picked</Label>}
|
564
|
+
<div className='grid grid-cols-1 md:grid-cols-3 gap-3'>
|
565
|
+
{getMostUsedBanksQuery?.data?.['BANK']?.map((fip: any) => (
|
566
|
+
<OuterCard
|
567
|
+
hoverEffect={false}
|
568
|
+
onSelect={() => {
|
569
|
+
if (fip.isEnabled) {
|
570
|
+
toggleFipSelection(fip.id)
|
571
|
+
}
|
572
|
+
}}
|
573
|
+
key={`fip-${fip.id}`}
|
574
|
+
className='cursor-pointer'
|
575
|
+
selected={selectedFips[activeCategory]?.includes(fip.id)}
|
576
|
+
>
|
577
|
+
<BankCard
|
578
|
+
loading={false}
|
579
|
+
bankName={fip.name || 'Unknown Provider'}
|
580
|
+
selectedTextColor=''
|
581
|
+
badgeText={false}
|
582
|
+
subText={false}
|
583
|
+
commingSoon={false}
|
584
|
+
rightSection={
|
585
|
+
<Checkbox
|
586
|
+
id={`checkbox-${fip.id}`}
|
587
|
+
className='cursor-pointer size-6'
|
588
|
+
checked={
|
589
|
+
selectedFips[activeCategory]?.includes(fip.id) ||
|
590
|
+
false
|
591
|
+
}
|
592
|
+
onCheckedChange={() => toggleFipSelection(fip.id)}
|
593
|
+
/>
|
594
|
+
}
|
595
|
+
image={fip.logoUrl || '/images/bank-logo.png'}
|
596
|
+
imageSize='w-8 h-8'
|
597
|
+
className='cursor-pointer'
|
598
|
+
/>
|
599
|
+
|
600
|
+
</OuterCard>
|
601
|
+
))}
|
602
|
+
|
603
|
+
</div>
|
604
|
+
</div>}
|
605
|
+
|
606
|
+
<div className='flex flex-col gap-2 mt-6'>
|
607
|
+
{activeCategoryFips.length > 0 && <Label className='text-sm md:text-md font-medium text-consent-secondary'>All financial information providers</Label>}
|
608
|
+
{activeCategoryFips.length === 0 ? (
|
609
|
+
<div className='text-center p-4 border rounded-lg border-dashed border-gray-300 text-gray-500'>
|
610
|
+
No {formatCategoryName(activeCategory)} providers found
|
611
|
+
</div>
|
612
|
+
) : (
|
613
|
+
<OuterCard hoverEffect={false} noAction={true}>
|
614
|
+
<div className='flex flex-col gap-4 w-full py-2'>
|
615
|
+
{activeCategoryFips.map((fip, index) => (
|
616
|
+
<>
|
617
|
+
<BankCard
|
618
|
+
loading={false}
|
619
|
+
bankName={fip.name || 'Unknown Provider'}
|
620
|
+
key={`fip-${fip.id}`}
|
621
|
+
selected={selectedFips[activeCategory]?.includes(fip.id)}
|
622
|
+
selectedTextColor=''
|
623
|
+
onClick={() => {
|
624
|
+
if (fip.isEnabled) {
|
625
|
+
toggleFipSelection(fip.id)
|
626
|
+
}
|
627
|
+
}}
|
628
|
+
subText={false}
|
629
|
+
badgeText={fip.isbadge ? 'New' : false}
|
630
|
+
commingSoon={!fip.isEnabled}
|
631
|
+
notify={accountsToNotify.includes(fip?.id)}
|
632
|
+
commingSoonFun={() => {
|
633
|
+
setAccountsToNotify(fip?.id)
|
634
|
+
trackEvent(EVENTS.NOTIFY_UPDATES, { searchValue: searchQuery, fipId: fip?.id, userId: decodedInfo?.userid })
|
635
|
+
}}
|
636
|
+
rightSection={
|
637
|
+
<Checkbox
|
638
|
+
id={`checkbox-${fip.id}`}
|
639
|
+
className='cursor-pointer size-6'
|
640
|
+
checked={
|
641
|
+
selectedFips[activeCategory]?.includes(fip.id) ||
|
642
|
+
false
|
643
|
+
}
|
644
|
+
onCheckedChange={() => toggleFipSelection(fip.id)}
|
645
|
+
/>
|
646
|
+
}
|
647
|
+
image={fip.logoUrl || '/images/bank-logo.png'}
|
648
|
+
imageSize='w-8 h-8'
|
649
|
+
className='cursor-pointer'
|
650
|
+
/>
|
651
|
+
{index !== activeCategoryFips.length - 1 && (
|
652
|
+
<div className='h-1 border-b border-gray-100 md:border-gray-200 dark:border-border' />
|
653
|
+
)}
|
654
|
+
</>
|
655
|
+
))}
|
656
|
+
</div>
|
657
|
+
</OuterCard>
|
658
|
+
)}
|
659
|
+
</div>
|
660
|
+
</>
|
661
|
+
)
|
662
|
+
}
|
663
|
+
|
664
|
+
const renderAddNumberButton = () => {
|
665
|
+
if (getMobileNumbersQuery?.data?.length >= 3) return null;
|
666
|
+
|
667
|
+
const button = (
|
668
|
+
<AnimatedButton
|
669
|
+
variant={'text'}
|
670
|
+
size={'text'}
|
671
|
+
className='text-primary'
|
672
|
+
onClick={() => openAddNumberModal(showModal.number)}
|
673
|
+
>
|
674
|
+
<PlusIcon /> {t('addNumber')}
|
675
|
+
</AnimatedButton>
|
676
|
+
);
|
677
|
+
|
678
|
+
if (isMobile) return button;
|
679
|
+
|
680
|
+
return (
|
681
|
+
<Modal
|
682
|
+
title={showModal.title}
|
683
|
+
open={showModal.isOpen}
|
684
|
+
className='w-[380px]'
|
685
|
+
onOpenChange={open =>
|
686
|
+
!open &&
|
687
|
+
setShowModal({
|
688
|
+
isOpen: open,
|
689
|
+
title: '',
|
690
|
+
content: null,
|
691
|
+
number: '',
|
692
|
+
otp: null,
|
693
|
+
error: ''
|
694
|
+
})
|
695
|
+
}
|
696
|
+
>
|
697
|
+
<Modal.Trigger>
|
698
|
+
{button}
|
699
|
+
</Modal.Trigger>
|
700
|
+
{showModal.content == 'otp' ? <div className='flex flex-col items-center gap-4'>
|
701
|
+
<OTPInputComponent
|
702
|
+
maxLength={4}
|
703
|
+
title=''
|
704
|
+
editable={true}
|
705
|
+
mobileNumber={showModal.number}
|
706
|
+
onResend={() => {
|
707
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
708
|
+
addNewNoQuery.mutate(undefined, {
|
709
|
+
onSuccess: () => {
|
710
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
711
|
+
},
|
712
|
+
onError: (error: Error) => {
|
713
|
+
setShowModal(old => ({ ...old, otp: null, error: error.response?.data?.errorMsg || 'Something went wrong' }))
|
714
|
+
// console.error(error)
|
715
|
+
}
|
716
|
+
})
|
717
|
+
}}
|
718
|
+
countdownTime={18}
|
719
|
+
error={Boolean(showModal.error)}
|
720
|
+
errorMessage={showModal.error}
|
721
|
+
onChange={otp => {
|
722
|
+
setShowModal(old => ({ ...old, otp, error: '' }))
|
723
|
+
}}
|
724
|
+
handleEdit={() => {
|
725
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
726
|
+
openAddNumberModal(showModal.number)
|
727
|
+
}}
|
728
|
+
/>
|
729
|
+
<AnimatedButton
|
730
|
+
className='w-full h-11'
|
731
|
+
onClick={() => {
|
732
|
+
verifyNewNoQuery.mutate()
|
733
|
+
}}
|
734
|
+
>
|
735
|
+
Next
|
736
|
+
</AnimatedButton>
|
737
|
+
</div> : showModal.content}
|
738
|
+
</Modal>
|
739
|
+
);
|
740
|
+
};
|
741
|
+
|
742
|
+
return (
|
743
|
+
<div className='flex flex-col gap-1 pb-16 md:pb-16'>
|
744
|
+
<div className='flex flex-col gap-1 w-full md:px-14 md:mt-10 sm:px-4'>
|
745
|
+
{/* Mobile number selection */}
|
746
|
+
<SectionTitle
|
747
|
+
className='md:mt-4 sm:mt-1'
|
748
|
+
title={`${t('mobileNo')}*`}
|
749
|
+
rightSection={renderAddNumberButton()}
|
750
|
+
/>
|
751
|
+
{/* <Label className='text-sm text-consent-secondary'>{t('mobileSubTitle')}</Label> */}
|
752
|
+
<RadioGroup
|
753
|
+
value={selectedMobileNumber}
|
754
|
+
onValueChange={setSelectedMobileNumber}
|
755
|
+
>
|
756
|
+
<div className='grid md:grid-cols-3 sm:grid-cols-1 gap-4'>
|
757
|
+
{getMobileNumbersQuery?.data?.map(
|
758
|
+
(item: { value: string }, index: number) => (
|
759
|
+
<OuterCard
|
760
|
+
key={`${item.value} - ${index}`}
|
761
|
+
selected={selectedMobileNumber === item.value.toString()}
|
762
|
+
onSelect={() =>
|
763
|
+
setSelectedMobileNumber(item.value.toString())
|
764
|
+
}
|
765
|
+
>
|
766
|
+
<div className='flex items-center justify-between w-full gap-2'>
|
767
|
+
<p
|
768
|
+
className={`text-md truncate font-medium ${selectedMobileNumber === item.value.toString()
|
769
|
+
? 'text-primary'
|
770
|
+
: ' dark:text-gray-400'
|
771
|
+
}`}
|
772
|
+
>
|
773
|
+
+91 {item.value}
|
774
|
+
</p>
|
775
|
+
<RadioGroupItem
|
776
|
+
value={item.value.toString()}
|
777
|
+
id={`radio-${item.value}`}
|
778
|
+
className={`cursor-pointer`}
|
779
|
+
/>
|
780
|
+
</div>
|
781
|
+
</OuterCard>
|
782
|
+
)
|
783
|
+
)}
|
784
|
+
</div>
|
785
|
+
</RadioGroup>
|
786
|
+
<p className='text-sm md:text-lg font-medium text-placeholder mt-2'>
|
787
|
+
{t('youCanAddUpTo3Numbers')}
|
788
|
+
</p>
|
789
|
+
|
790
|
+
{renderCategoryContent()}
|
791
|
+
<DummyFooter show={true} />
|
792
|
+
</div>
|
793
|
+
|
794
|
+
{/* Bottom sheet for mobile */}
|
795
|
+
<MobileFooter show={!!(isMobile && showBottomSheet)}>
|
796
|
+
<div className='flex flex-col items-center w-full gap-4'>
|
797
|
+
<AnimatedButton onClick={handleNext} size='lg' className='w-full h-[50px]'>
|
798
|
+
{t('discoverAccounts')}
|
799
|
+
</AnimatedButton>
|
800
|
+
<AnimatedButton
|
801
|
+
// onClick={() => setActiveCategory(null)}
|
802
|
+
onClick={() => clearSelections()}
|
803
|
+
variant={'ghost'}
|
804
|
+
>
|
805
|
+
{t('cancel')}
|
806
|
+
</AnimatedButton>
|
807
|
+
</div>
|
808
|
+
</MobileFooter>
|
809
|
+
|
810
|
+
{/* Footer for desktop */}
|
811
|
+
<WebFooter show={!!(!isMobile && showBottomSheet)}>
|
812
|
+
<AnimatedButton
|
813
|
+
onClick={() => clearSelections()}
|
814
|
+
variant={'ghost'}
|
815
|
+
size={'lg'}
|
816
|
+
className='text-primary'
|
817
|
+
>
|
818
|
+
{t('cancel')}
|
819
|
+
</AnimatedButton>
|
820
|
+
<AnimatedButton onClick={handleNext} size='lg'>
|
821
|
+
{t('discoverAccounts')}
|
822
|
+
</AnimatedButton>
|
823
|
+
</WebFooter>
|
824
|
+
|
825
|
+
<BottomSheet
|
826
|
+
isOpen={isMobile && showModal.isOpen}
|
827
|
+
onClose={() => setShowModal(prev => ({ ...prev, isOpen: false, error: '' }))}
|
828
|
+
title={showModal.title}
|
829
|
+
height='auto'
|
830
|
+
showHandle={true}
|
831
|
+
showCloseButton={false}
|
832
|
+
>
|
833
|
+
{showModal.content == 'otp' ? <div className='flex flex-col items-center gap-4'>
|
834
|
+
<OTPInputComponent
|
835
|
+
maxLength={4}
|
836
|
+
title=''
|
837
|
+
editable={true}
|
838
|
+
mobileNumber={showModal.number}
|
839
|
+
onResend={() => {
|
840
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
841
|
+
addNewNoQuery.mutate(undefined, {
|
842
|
+
onSuccess: () => {
|
843
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
844
|
+
},
|
845
|
+
onError: (error: Error) => {
|
846
|
+
setShowModal(old => ({ ...old, otp: null, error: error.response?.data?.errorMsg || 'Something went wrong' }))
|
847
|
+
// console.error(error)
|
848
|
+
}
|
849
|
+
})
|
850
|
+
}}
|
851
|
+
countdownTime={18}
|
852
|
+
error={Boolean(showModal.error)}
|
853
|
+
errorMessage={showModal.error}
|
854
|
+
onChange={otp => {
|
855
|
+
setShowModal(old => ({ ...old, otp, error: '' }))
|
856
|
+
}}
|
857
|
+
handleEdit={() => {
|
858
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
859
|
+
openAddNumberModal(showModal.number)
|
860
|
+
}}
|
861
|
+
/>
|
862
|
+
<AnimatedButton
|
863
|
+
className='w-full h-11'
|
864
|
+
onClick={() => {
|
865
|
+
verifyNewNoQuery.mutate()
|
866
|
+
}}
|
867
|
+
>
|
868
|
+
Next
|
869
|
+
</AnimatedButton>
|
870
|
+
</div> : showModal.content}
|
871
|
+
</BottomSheet>
|
872
|
+
|
873
|
+
<Modal
|
874
|
+
open={showCloseModal.isOpen}
|
875
|
+
withCloseIcon={false}
|
876
|
+
onOpenChange={open => !open && setShowCloseModal({ isOpen: open })}
|
877
|
+
className='w-[380px]'
|
878
|
+
>
|
879
|
+
<div className='flex flex-col gap-4 items-center'>
|
880
|
+
<div className='flex items-center justify-center bg-red-100 p-2.5 rounded-full'>
|
881
|
+
<InfoIcon className='h-[28px] w-[28px] text-red-700' />
|
882
|
+
</div>
|
883
|
+
<div className='flex flex-col gap-2 items-center text-center mt-2'>
|
884
|
+
<p className='text-xl font-semibold'>
|
885
|
+
Are you sure you want to exit?
|
886
|
+
</p>
|
887
|
+
<p className='text-sm text-muted-secondary'>
|
888
|
+
By clicking on exit you decide not to share any financial accounts
|
889
|
+
</p>
|
890
|
+
</div>
|
891
|
+
<div className='flex gap-2 w-full items-center justify-center'>
|
892
|
+
<AnimatedButton
|
893
|
+
size='lg'
|
894
|
+
onClick={() => setShowCloseModal({ isOpen: false })}
|
895
|
+
>
|
896
|
+
No, go back!
|
897
|
+
</AnimatedButton>
|
898
|
+
<AnimatedButton
|
899
|
+
size='lg'
|
900
|
+
variant='ghost'
|
901
|
+
className='hover:bg-red-100 hover:text-red-700 text-red-700'
|
902
|
+
onClick={handleTabClose}
|
903
|
+
>
|
904
|
+
Yes, exit
|
905
|
+
</AnimatedButton>
|
906
|
+
</div>
|
907
|
+
</div>
|
908
|
+
</Modal>
|
909
|
+
</div>
|
910
|
+
)
|
911
|
+
}
|
912
|
+
|
913
|
+
export default LinkAccounts
|