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,751 @@
|
|
1
|
+
import AlertComp from '@/components/alert/alert'
|
2
|
+
import OuterCard from '@/components/cards/OuterCard'
|
3
|
+
import SectionTitle from '@/components/title/SectionTitle'
|
4
|
+
import { AnimatedButton } from '@/components/ui/animatedButton'
|
5
|
+
import { Input } from '@/components/ui/input'
|
6
|
+
import { Label } from '@/components/ui/label'
|
7
|
+
import { MobileFooter } from '@/components/ui/mobile-footer'
|
8
|
+
import Modal from '@/components/ui/modal'
|
9
|
+
import { OTPInputComponent } from '@/components/ui/otp-input'
|
10
|
+
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
11
|
+
import WebFooter from '@/components/ui/web-footer'
|
12
|
+
import { Info, PlusIcon, X } from 'lucide-react'
|
13
|
+
import { useEffect, useMemo, useState } from 'react'
|
14
|
+
import { useNavigate } from 'react-router-dom'
|
15
|
+
import { trackEvent, EVENTS } from '@/utils/posthog'
|
16
|
+
import { useTranslation } from 'react-i18next'
|
17
|
+
import { useMutation, useQuery } from '@tanstack/react-query'
|
18
|
+
import { accountService } from '@/services/api'
|
19
|
+
import { useMediaQuery } from '@/hooks/use-media-query'
|
20
|
+
import { useFipStore } from '@/store/fip.store'
|
21
|
+
import logo from '../../assets/brand/saafe-logo.svg'
|
22
|
+
import { z } from 'zod'
|
23
|
+
import { Calendar } from '@/components/ui/calendar'
|
24
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
25
|
+
import { CalendarIcon } from 'lucide-react'
|
26
|
+
import { DropdownNavProps } from 'react-day-picker'
|
27
|
+
import {
|
28
|
+
Select,
|
29
|
+
SelectContent,
|
30
|
+
SelectItem,
|
31
|
+
SelectTrigger,
|
32
|
+
SelectValue,
|
33
|
+
} from "@/components/ui/select";
|
34
|
+
import { DropdownProps } from 'react-day-picker'
|
35
|
+
import { useSetPageTitle } from '@/hooks/use-page-title'
|
36
|
+
import { useRedirectStore } from '@/store/redirect.store'
|
37
|
+
import { BottomSheet } from '@/components/ui/bottom-sheet'
|
38
|
+
|
39
|
+
interface DiscoverAccountProps {
|
40
|
+
state?: {
|
41
|
+
category: string
|
42
|
+
selectedFips?: string[]
|
43
|
+
}
|
44
|
+
currentCategory?: string | null
|
45
|
+
}
|
46
|
+
|
47
|
+
const AddNumberModalContent = ({ showModal, setShowModal, t, addNewNoQuery, handleAddNumber }: {
|
48
|
+
showModal: { number: string; error: string };
|
49
|
+
setShowModal: React.Dispatch<React.SetStateAction<{
|
50
|
+
isOpen: boolean;
|
51
|
+
title: string;
|
52
|
+
content: React.ReactNode | null;
|
53
|
+
number: string;
|
54
|
+
otp: string | null;
|
55
|
+
error: string;
|
56
|
+
}>>;
|
57
|
+
t: (key: string) => string;
|
58
|
+
addNewNoQuery: {
|
59
|
+
mutate: (data: undefined, options: {
|
60
|
+
onSuccess: () => void;
|
61
|
+
onError: (error: Error) => void;
|
62
|
+
}) => void;
|
63
|
+
};
|
64
|
+
handleAddNumber: (number: string) => void;
|
65
|
+
}) => {
|
66
|
+
const [inputValue, setInputValue] = useState({ number: showModal.number, error: showModal.error });
|
67
|
+
|
68
|
+
useEffect(() => {
|
69
|
+
setInputValue({ number: showModal.number, error: showModal.error });
|
70
|
+
}, [showModal.number]);
|
71
|
+
|
72
|
+
return useMemo(() => (
|
73
|
+
<div className='flex flex-col items-center gap-5'>
|
74
|
+
<p className='text-base font-normal dark:text-muted-secondary'>
|
75
|
+
{t('pleaseEnterTheNewNumberYouWishToLinkBelow')}
|
76
|
+
</p>
|
77
|
+
<Input
|
78
|
+
type="tel"
|
79
|
+
inputMode="numeric"
|
80
|
+
className='h-12'
|
81
|
+
placeholder={t('mobileNoPlaceholder')}
|
82
|
+
maxLength={10}
|
83
|
+
value={inputValue.number}
|
84
|
+
onChange={e => {
|
85
|
+
const newValue = e.target.value;
|
86
|
+
const numericValue = newValue.replace(/[^0-9]/g, '');
|
87
|
+
setInputValue({ number: numericValue, error: '' });
|
88
|
+
setShowModal(prev => ({
|
89
|
+
...prev,
|
90
|
+
number: numericValue,
|
91
|
+
error: numericValue.length === 10 ? '' : 'Please enter a valid 10-digit mobile number'
|
92
|
+
}));
|
93
|
+
}}
|
94
|
+
/>
|
95
|
+
{inputValue.error && (
|
96
|
+
<p className="text-red-500 text-sm mt-[-20px]">{inputValue.error}</p>
|
97
|
+
)}
|
98
|
+
<AnimatedButton
|
99
|
+
className='w-full h-11 mt-2'
|
100
|
+
onClick={() => {
|
101
|
+
if (inputValue.number.length !== 10) {
|
102
|
+
setInputValue({ number: inputValue.number, error: 'Please enter a valid 10-digit mobile number' });
|
103
|
+
setShowModal(prev => ({ ...prev, error: 'Please enter a valid 10-digit mobile number' }));
|
104
|
+
return;
|
105
|
+
}
|
106
|
+
addNewNoQuery.mutate(undefined, {
|
107
|
+
onSuccess: () => {
|
108
|
+
handleAddNumber(inputValue.number)
|
109
|
+
},
|
110
|
+
onError: (error: Error) => {
|
111
|
+
// console.error(error)
|
112
|
+
setInputValue({ number: inputValue.number, error: error.response?.data?.errorCode || 'Something went wrong' });
|
113
|
+
}
|
114
|
+
})
|
115
|
+
}}
|
116
|
+
>
|
117
|
+
{t('login.getOtp')}
|
118
|
+
</AnimatedButton>
|
119
|
+
</div>
|
120
|
+
), [inputValue, showModal.error, t, setShowModal, addNewNoQuery, handleAddNumber])
|
121
|
+
}
|
122
|
+
|
123
|
+
const aadhaarSchema = z.object({
|
124
|
+
value: z.string()
|
125
|
+
.min(12, 'Aadhaar number must be 12 digits')
|
126
|
+
.max(12, 'Aadhaar number must be 12 digits')
|
127
|
+
.regex(/^\d+$/, 'Aadhaar number must contain only digits')
|
128
|
+
})
|
129
|
+
|
130
|
+
const panSchema = z.object({
|
131
|
+
value: z.string()
|
132
|
+
.min(10, 'PAN number must be 10 characters')
|
133
|
+
.max(10, 'PAN number must be 10 characters')
|
134
|
+
.regex(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/, 'Invalid PAN format. Format: ABCDE1234F')
|
135
|
+
})
|
136
|
+
|
137
|
+
const DiscoverAccount = ({ state, currentCategory }: DiscoverAccountProps) => {
|
138
|
+
useSetPageTitle('Discover accounts')
|
139
|
+
const navigate = useNavigate()
|
140
|
+
const { decodedInfo } = useRedirectStore()
|
141
|
+
const category = state?.category || currentCategory || 'BANKS'
|
142
|
+
const { t } = useTranslation()
|
143
|
+
const [validationError, setValidationError] = useState<{ [key: string]: string | null }>({
|
144
|
+
PAN: null,
|
145
|
+
AADHAAR: null
|
146
|
+
})
|
147
|
+
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
|
148
|
+
const [showAlert, setShowAlert] = useState(true)
|
149
|
+
|
150
|
+
// Format category name for display
|
151
|
+
const formatCategoryName = (name: string) => {
|
152
|
+
return name
|
153
|
+
.split('_')
|
154
|
+
.map(word => word.charAt(0) + word.slice(1).toLowerCase())
|
155
|
+
.join(' ')
|
156
|
+
}
|
157
|
+
const [showModal, setShowModal] = useState({
|
158
|
+
isOpen: false,
|
159
|
+
title: t('addNewMobileNumber'),
|
160
|
+
content: null as React.ReactNode,
|
161
|
+
number: '',
|
162
|
+
otp: null as string | null
|
163
|
+
})
|
164
|
+
|
165
|
+
// Detect mobile screens
|
166
|
+
const isMobile = useMediaQuery('(max-width: 768px)')
|
167
|
+
|
168
|
+
// FIP data from store
|
169
|
+
const {
|
170
|
+
selectedMobileNumber,
|
171
|
+
setSelectedMobileNumber,
|
172
|
+
identifiers,
|
173
|
+
setIdentifiers
|
174
|
+
} = useFipStore()
|
175
|
+
|
176
|
+
const getMobileNumbersQuery = useQuery({
|
177
|
+
queryKey: ['mobile-numbers'],
|
178
|
+
queryFn: async () => accountService.getMobileNumbers()
|
179
|
+
})
|
180
|
+
|
181
|
+
const addNewNoQuery = useMutation({
|
182
|
+
mutationFn: async () =>
|
183
|
+
accountService.addNewNumber({
|
184
|
+
value: showModal?.number || '',
|
185
|
+
type: 'MOBILE',
|
186
|
+
categoryType: 'STRONG'
|
187
|
+
}),
|
188
|
+
onSuccess: () => {
|
189
|
+
getMobileNumbersQuery.refetch()
|
190
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
191
|
+
},
|
192
|
+
onError: error => {
|
193
|
+
setShowModal(old => ({ ...old, otp: null, error: error.response?.data?.errorCode || 'Something went wrong' }))
|
194
|
+
}
|
195
|
+
})
|
196
|
+
|
197
|
+
const verifyNewNoQuery = useMutation({
|
198
|
+
mutationFn: async () =>
|
199
|
+
accountService.verifyNewNumber({
|
200
|
+
phoneNumber: showModal?.number || '',
|
201
|
+
code: showModal?.otp || '',
|
202
|
+
otpUniqueID: addNewNoQuery?.data?.mobile?.otpUniqueID || '',
|
203
|
+
identifierType: 'MOBILE'
|
204
|
+
}),
|
205
|
+
onSuccess: () => {
|
206
|
+
getMobileNumbersQuery.refetch()
|
207
|
+
setShowModal({
|
208
|
+
isOpen: false,
|
209
|
+
title: 'Add new mobile number',
|
210
|
+
content: null,
|
211
|
+
number: '',
|
212
|
+
otp: null
|
213
|
+
})
|
214
|
+
},
|
215
|
+
onError: error => {
|
216
|
+
setShowModal(old => ({ ...old, otp: null, error: error.response?.data?.errorCode || 'Something went wrong' }))
|
217
|
+
// console.error(error)
|
218
|
+
}
|
219
|
+
})
|
220
|
+
|
221
|
+
|
222
|
+
const handleAddNumber = (value: string) => {
|
223
|
+
|
224
|
+
setShowModal(old => ({
|
225
|
+
...old,
|
226
|
+
isOpen: true,
|
227
|
+
title: t('verifyNewMobileNumber'),
|
228
|
+
content: 'otp',
|
229
|
+
otp: null,
|
230
|
+
error: ''
|
231
|
+
}))
|
232
|
+
}
|
233
|
+
|
234
|
+
const openAddNumberModal = (currentNumber: string) => {
|
235
|
+
setShowModal(prev => ({
|
236
|
+
...prev,
|
237
|
+
isOpen: true,
|
238
|
+
title: t('addNewMobileNumber'),
|
239
|
+
content: <AddNumberModalContent
|
240
|
+
showModal={{ ...showModal, number: currentNumber ? currentNumber : showModal.number }}
|
241
|
+
setShowModal={setShowModal}
|
242
|
+
t={t}
|
243
|
+
addNewNoQuery={addNewNoQuery}
|
244
|
+
handleAddNumber={(number: string) => handleAddNumber(number)}
|
245
|
+
/>,
|
246
|
+
otp: null,
|
247
|
+
error: ''
|
248
|
+
}))
|
249
|
+
}
|
250
|
+
|
251
|
+
const handleDiscoverAccount = () => {
|
252
|
+
trackEvent(EVENTS.DISCOVER_ACCOUNT, { category })
|
253
|
+
// After discovering accounts, go back to account selection page
|
254
|
+
const result = [...identifiers]
|
255
|
+
const mobileIndex = result.findIndex(i => i.type === 'MOBILE')
|
256
|
+
result.splice(mobileIndex, 1, {
|
257
|
+
...result[mobileIndex],
|
258
|
+
value: selectedMobileNumber
|
259
|
+
})
|
260
|
+
navigate('/link-accounts/discovery', {
|
261
|
+
state: {
|
262
|
+
category,
|
263
|
+
selectedFips: state?.selectedFips,
|
264
|
+
fromDiscovery: true
|
265
|
+
}
|
266
|
+
})
|
267
|
+
}
|
268
|
+
|
269
|
+
const handleCancel = () => {
|
270
|
+
// Go back to account selection
|
271
|
+
navigate('/link-accounts/banks', {
|
272
|
+
state: {
|
273
|
+
category,
|
274
|
+
selectedFips: state?.selectedFips
|
275
|
+
}
|
276
|
+
})
|
277
|
+
}
|
278
|
+
|
279
|
+
const handleCalendarChange = (
|
280
|
+
_value: string | number,
|
281
|
+
_e: React.ChangeEventHandler<HTMLSelectElement>,
|
282
|
+
) => {
|
283
|
+
const _event = {
|
284
|
+
target: {
|
285
|
+
value: String(_value),
|
286
|
+
},
|
287
|
+
} as React.ChangeEvent<HTMLSelectElement>;
|
288
|
+
_e(_event);
|
289
|
+
};
|
290
|
+
|
291
|
+
const renderAddNumberButton = () => {
|
292
|
+
if (getMobileNumbersQuery?.data?.length >= 3) return null;
|
293
|
+
|
294
|
+
const button = (
|
295
|
+
<AnimatedButton
|
296
|
+
variant={'text'}
|
297
|
+
size={'text'}
|
298
|
+
className='text-primary'
|
299
|
+
onClick={() => openAddNumberModal(showModal.number)}
|
300
|
+
>
|
301
|
+
<PlusIcon /> {t('addNumber')}
|
302
|
+
</AnimatedButton>
|
303
|
+
);
|
304
|
+
|
305
|
+
if (isMobile) return button;
|
306
|
+
|
307
|
+
return (
|
308
|
+
<Modal
|
309
|
+
title={showModal.title}
|
310
|
+
open={showModal.isOpen}
|
311
|
+
className='w-[380px]'
|
312
|
+
onOpenChange={open =>
|
313
|
+
!open &&
|
314
|
+
setShowModal({
|
315
|
+
isOpen: open,
|
316
|
+
title: '',
|
317
|
+
content: null,
|
318
|
+
number: '',
|
319
|
+
otp: null,
|
320
|
+
error: ''
|
321
|
+
})
|
322
|
+
}
|
323
|
+
>
|
324
|
+
<Modal.Trigger>
|
325
|
+
{button}
|
326
|
+
</Modal.Trigger>
|
327
|
+
{showModal.content == 'otp' ? <div className='flex flex-col items-center gap-4'>
|
328
|
+
<OTPInputComponent
|
329
|
+
maxLength={4}
|
330
|
+
title=''
|
331
|
+
editable={true}
|
332
|
+
mobileNumber={showModal.number}
|
333
|
+
onResend={() => {
|
334
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
335
|
+
addNewNoQuery.mutate(undefined, {
|
336
|
+
onSuccess: () => {
|
337
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
338
|
+
},
|
339
|
+
onError: (error: Error) => {
|
340
|
+
setShowModal(old => ({ ...old, otp: null, error: error.response?.data?.errorMsg || 'Something went wrong' }))
|
341
|
+
// console.error(error)
|
342
|
+
}
|
343
|
+
})
|
344
|
+
}}
|
345
|
+
countdownTime={18}
|
346
|
+
error={Boolean(showModal.error)}
|
347
|
+
errorMessage={showModal.error}
|
348
|
+
onChange={otp => {
|
349
|
+
setShowModal(old => ({ ...old, otp, error: '' }))
|
350
|
+
}}
|
351
|
+
handleEdit={() => {
|
352
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
353
|
+
openAddNumberModal(showModal.number)
|
354
|
+
}}
|
355
|
+
/>
|
356
|
+
<AnimatedButton
|
357
|
+
className='w-full h-11'
|
358
|
+
onClick={() => {
|
359
|
+
verifyNewNoQuery.mutate()
|
360
|
+
}}
|
361
|
+
>
|
362
|
+
Next
|
363
|
+
</AnimatedButton>
|
364
|
+
</div> : showModal.content}
|
365
|
+
</Modal>
|
366
|
+
);
|
367
|
+
};
|
368
|
+
|
369
|
+
return (
|
370
|
+
<div>
|
371
|
+
<div className='flex flex-col gap-1 w-full md:px-14 px-0 gap-2 md:mt-6 mt-0'>
|
372
|
+
{showAlert &&
|
373
|
+
<div className='mb-4'>
|
374
|
+
<AlertComp
|
375
|
+
icon={
|
376
|
+
<div className='flex items-center justify-center bg-yellow-600 p-2 rounded-full'>
|
377
|
+
<Info
|
378
|
+
strokeWidth='2.3'
|
379
|
+
className='h-[18px] w-[18px] text-white'
|
380
|
+
/>
|
381
|
+
</div>
|
382
|
+
}
|
383
|
+
title={t('noAccountsFound')}
|
384
|
+
description={t('noAccountsFoundDescription')}
|
385
|
+
rightSection={
|
386
|
+
<AnimatedButton variant={'ghost'} size={'sm'} onClick={() => setShowAlert(false)}>
|
387
|
+
<X />
|
388
|
+
</AnimatedButton>
|
389
|
+
}
|
390
|
+
/>
|
391
|
+
</div>
|
392
|
+
}
|
393
|
+
<div className='flex items-center justify-between gap-2'>
|
394
|
+
<p className='text-md md:text-2xl font-semibold text-black dark:text-white'>
|
395
|
+
{t('keywords.Discover') +
|
396
|
+
' ' +
|
397
|
+
t('keywords.your') +
|
398
|
+
' ' +
|
399
|
+
t(`keywords.accounts`)}
|
400
|
+
</p>
|
401
|
+
<Modal title={false} withCloseIcon={true} className='w-[450px]'>
|
402
|
+
<Modal.Trigger>
|
403
|
+
<AnimatedButton variant={'ghost'} size={'sm'}>
|
404
|
+
<Info />
|
405
|
+
</AnimatedButton>
|
406
|
+
</Modal.Trigger>
|
407
|
+
<div className='flex flex-col items-center gap-4'>
|
408
|
+
<div className='flex items-center justify-center bg-gray-200/50 dark:bg-gray-100/50 p-3 rounded-full'>
|
409
|
+
<Info className='h-[32px] w-[32px]' />
|
410
|
+
</div>
|
411
|
+
<p className='text-[20px] md:text-2xl text-gray-900 font-semibold dark:text-white'>
|
412
|
+
Secure & Temporary Data Use
|
413
|
+
</p>
|
414
|
+
<Label className='text-sm md:text-lg text-gray-500 dark:text-gray-400 font-light'>
|
415
|
+
We use your PAN and Date of Birth only to discover your
|
416
|
+
accounts. This information is not stored and is used solely for
|
417
|
+
this process
|
418
|
+
</Label>
|
419
|
+
<Modal.Close className='w-full'>
|
420
|
+
<AnimatedButton className='w-full'>Yes, I Understand</AnimatedButton>
|
421
|
+
</Modal.Close>
|
422
|
+
</div>
|
423
|
+
</Modal>
|
424
|
+
</div>
|
425
|
+
<p className='text-sm md:text-lg text-consent-secondary mt-0 md:mt-1'>
|
426
|
+
Enter the following details as linked to your{' '}
|
427
|
+
{formatCategoryName(category).toLowerCase()} accounts
|
428
|
+
</p>
|
429
|
+
<SectionTitle
|
430
|
+
className='mt-4 mb-1'
|
431
|
+
title={t('mobileNo')}
|
432
|
+
rightSection={renderAddNumberButton()}
|
433
|
+
/>
|
434
|
+
<Label className='text-sm text-consent-secondary font-light'>{t('mobileSubTitle')}</Label>
|
435
|
+
<div className='flex flex-col gap-1 w-full gap-2'>
|
436
|
+
<RadioGroup
|
437
|
+
value={selectedMobileNumber}
|
438
|
+
onValueChange={setSelectedMobileNumber}
|
439
|
+
>
|
440
|
+
<div className='grid md:grid-cols-3 sm:grid-cols-1 gap-4'>
|
441
|
+
{getMobileNumbersQuery?.data?.map(
|
442
|
+
(item: { value: string }, index: number) => (
|
443
|
+
<OuterCard
|
444
|
+
key={`${item.value} - ${index}`}
|
445
|
+
selected={selectedMobileNumber === item.value.toString()}
|
446
|
+
onSelect={() =>
|
447
|
+
setSelectedMobileNumber(item.value.toString())
|
448
|
+
}
|
449
|
+
>
|
450
|
+
<div className='flex items-center justify-between w-full gap-2'>
|
451
|
+
<p
|
452
|
+
className={`text-md truncate font-medium ${selectedMobileNumber == item.value.toString()
|
453
|
+
? 'text-primary'
|
454
|
+
: ''
|
455
|
+
}`}
|
456
|
+
>
|
457
|
+
{item.value}
|
458
|
+
</p>
|
459
|
+
<RadioGroupItem
|
460
|
+
value={item.value.toString()}
|
461
|
+
id={`radio-${item.value}`}
|
462
|
+
/>
|
463
|
+
</div>
|
464
|
+
</OuterCard>
|
465
|
+
)
|
466
|
+
)}
|
467
|
+
</div>
|
468
|
+
</RadioGroup>
|
469
|
+
<p className='text-sm text-muted-secondary font-light mt-1'>
|
470
|
+
{t('youCanAddUpTo3Numbers')}
|
471
|
+
</p>
|
472
|
+
</div>
|
473
|
+
{identifiers?.find(i => i.type == 'DOB') ? (
|
474
|
+
<>
|
475
|
+
<SectionTitle className='mt-6' title={`DOB*`} />
|
476
|
+
<div className='flex flex-col gap-1 md:w-[50%] w-full gap-2'>
|
477
|
+
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
478
|
+
<PopoverTrigger asChild>
|
479
|
+
<div className='flex h-[56px] w-full items-center justify-between rounded-md border bg-white border-gray-200 dark:border-gray-700 dark:bg-input/30 px-3 py-2 text-consent-primary dark:border-gray-700'>
|
480
|
+
{identifiers?.find(i => i.type === 'DOB')?.value ? (
|
481
|
+
<span>{new Date(identifiers.find(i => i.type === 'DOB')?.value || '').toLocaleDateString('en-IN', { year: 'numeric', month: 'long', day: 'numeric' })}</span>
|
482
|
+
) : (
|
483
|
+
<span className='text-muted-foreground'>Pick a date</span>
|
484
|
+
)}
|
485
|
+
<CalendarIcon className='h-4 w-4 opacity-50' />
|
486
|
+
</div>
|
487
|
+
|
488
|
+
</PopoverTrigger>
|
489
|
+
<PopoverContent className='w-auto p-0' align='start'>
|
490
|
+
<Calendar
|
491
|
+
mode='single'
|
492
|
+
captionLayout="dropdown"
|
493
|
+
hideNavigation
|
494
|
+
|
495
|
+
components={{
|
496
|
+
DropdownNav: (props: DropdownNavProps) => {
|
497
|
+
return <div className="flex w-full items-center gap-2">{props.children}</div>;
|
498
|
+
},
|
499
|
+
|
500
|
+
Dropdown: (props: DropdownProps) => {
|
501
|
+
return (
|
502
|
+
<Select
|
503
|
+
value={String(props.value)}
|
504
|
+
onValueChange={(value) => {
|
505
|
+
if (props.onChange) {
|
506
|
+
handleCalendarChange(value, props.onChange);
|
507
|
+
}
|
508
|
+
}}
|
509
|
+
>
|
510
|
+
<SelectTrigger className="h-8 w-fit font-medium first:grow">
|
511
|
+
<SelectValue />
|
512
|
+
</SelectTrigger>
|
513
|
+
<SelectContent className="max-h-[min(26rem,var(--radix-select-content-available-height))]">
|
514
|
+
{props.options?.map((option) => (
|
515
|
+
|
516
|
+
<SelectItem
|
517
|
+
key={option.value}
|
518
|
+
value={String(option.value)}
|
519
|
+
disabled={option.disabled}
|
520
|
+
>
|
521
|
+
{option.label}
|
522
|
+
</SelectItem>
|
523
|
+
))}
|
524
|
+
</SelectContent>
|
525
|
+
</Select>
|
526
|
+
);
|
527
|
+
},
|
528
|
+
}}
|
529
|
+
selected={identifiers?.find(i => i.type === 'DOB')?.value ? new Date(identifiers.find(i => i.type === 'DOB')?.value || '') : undefined}
|
530
|
+
onSelect={(date) => {
|
531
|
+
if (date) {
|
532
|
+
const result = [...identifiers]
|
533
|
+
result.splice(
|
534
|
+
result.findIndex(i => i.type === 'DOB'),
|
535
|
+
1,
|
536
|
+
{
|
537
|
+
...identifiers?.find(i => i.type === 'DOB'),
|
538
|
+
value: date.toLocaleString().split(',')[0]
|
539
|
+
}
|
540
|
+
)
|
541
|
+
setIdentifiers(result)
|
542
|
+
setIsPopoverOpen(false)
|
543
|
+
}
|
544
|
+
}}
|
545
|
+
initialFocus
|
546
|
+
/>
|
547
|
+
</PopoverContent>
|
548
|
+
</Popover>
|
549
|
+
</div>
|
550
|
+
</>
|
551
|
+
) : null}
|
552
|
+
{identifiers?.find(i => i.type == 'PAN') ? (
|
553
|
+
<>
|
554
|
+
<SectionTitle className='mt-6' title={`PAN*`} />
|
555
|
+
<div className='flex flex-col gap-1 md:w-[50%] w-full gap-2'>
|
556
|
+
<Input
|
557
|
+
type='text'
|
558
|
+
className={`h-[56px] bg-white border-gray-200 dark:border-gray-700 text-consent-primary text-md ${validationError.PAN ? 'border-red-500' : ''}`}
|
559
|
+
placeholder={`Enter PAN`}
|
560
|
+
value={identifiers?.find(i => i.type === 'PAN')?.value}
|
561
|
+
readOnly={decodedInfo?.pan ? true : false}
|
562
|
+
onChange={e => {
|
563
|
+
setValidationError(prev => ({ ...prev, PAN: null }))
|
564
|
+
const panValue = e.target.value.replace(/[^A-Za-z0-9]/g, '').toUpperCase()
|
565
|
+
const result = [...identifiers]
|
566
|
+
result.splice(
|
567
|
+
result.findIndex(i => i.type === 'PAN'),
|
568
|
+
1,
|
569
|
+
{
|
570
|
+
...identifiers?.find(i => i.type === 'PAN'),
|
571
|
+
value: panValue
|
572
|
+
}
|
573
|
+
)
|
574
|
+
setIdentifiers(result)
|
575
|
+
|
576
|
+
// Only validate when input reaches max length
|
577
|
+
if (panValue.length >= 10) {
|
578
|
+
try {
|
579
|
+
panSchema.parse({ value: panValue })
|
580
|
+
} catch (error) {
|
581
|
+
if (error instanceof z.ZodError) {
|
582
|
+
setValidationError(prev => ({ ...prev, PAN: error.errors[0].message }))
|
583
|
+
// console.error('PAN validation error:', error.errors[0].message)
|
584
|
+
}
|
585
|
+
}
|
586
|
+
}
|
587
|
+
}}
|
588
|
+
/>
|
589
|
+
{validationError.PAN && (
|
590
|
+
<p className="text-sm text-red-500 mt-1">{validationError.PAN}</p>
|
591
|
+
)}
|
592
|
+
</div>
|
593
|
+
</>
|
594
|
+
) : null}
|
595
|
+
{identifiers?.find(i => i.type == 'AADHAAR') ? (
|
596
|
+
<>
|
597
|
+
<SectionTitle className='mt-6' title={`AADHAAR*`} />
|
598
|
+
<div className='flex flex-col gap-1 md:w-[50%] w-full gap-2'>
|
599
|
+
<Input
|
600
|
+
type='text'
|
601
|
+
className={`h-[56px] bg-white border-gray-200 dark:border-gray-700 text-consent-primary text-md ${validationError.AADHAAR ? 'border-red-500' : ''}`}
|
602
|
+
placeholder={`Enter AADHAAR`}
|
603
|
+
readOnly={decodedInfo?.aadhaar ? true : false}
|
604
|
+
value={identifiers?.find(i => i.type === 'AADHAAR')?.value}
|
605
|
+
onChange={e => {
|
606
|
+
setValidationError(prev => ({ ...prev, AADHAAR: null }))
|
607
|
+
const aadhaarValue = e.target.value.replace(/[^0-9]/g, '')
|
608
|
+
const result = [...identifiers]
|
609
|
+
result.splice(
|
610
|
+
result.findIndex(i => i.type === 'AADHAAR'),
|
611
|
+
1,
|
612
|
+
{
|
613
|
+
...identifiers?.find(i => i.type === 'AADHAAR'),
|
614
|
+
value: aadhaarValue
|
615
|
+
}
|
616
|
+
)
|
617
|
+
setIdentifiers(result)
|
618
|
+
|
619
|
+
// Only validate when input reaches max length
|
620
|
+
if (aadhaarValue.length >= 12) {
|
621
|
+
try {
|
622
|
+
aadhaarSchema.parse({ value: aadhaarValue })
|
623
|
+
} catch (error) {
|
624
|
+
if (error instanceof z.ZodError) {
|
625
|
+
setValidationError(prev => ({ ...prev, AADHAAR: error.errors[0].message }))
|
626
|
+
// console.error('Aadhaar validation error:', error.errors[0].message)
|
627
|
+
}
|
628
|
+
}
|
629
|
+
}
|
630
|
+
}}
|
631
|
+
/>
|
632
|
+
{validationError.AADHAAR && (
|
633
|
+
<p className="text-sm text-red-500 mt-1">{validationError.AADHAAR}</p>
|
634
|
+
)}
|
635
|
+
</div>
|
636
|
+
</>
|
637
|
+
) : null}
|
638
|
+
|
639
|
+
{/* <div className='mt-16'>
|
640
|
+
<img src={logo} className='w-48 h-auto filter grayscale opacity-30' />
|
641
|
+
<h1 className='text-4xl font-semibold text-gray-300 mt-8 dark:text-muted-secondary'>
|
642
|
+
Trusted by over 5,00,000+ Indians
|
643
|
+
</h1>
|
644
|
+
</div> */}
|
645
|
+
</div>
|
646
|
+
|
647
|
+
<MobileFooter show={identifiers?.filter(i => !i.value || i.value === '').length === 0 && (() => {
|
648
|
+
try {
|
649
|
+
identifiers.find(i => i.type === 'PAN') && panSchema.parse({ value: identifiers?.find(i => i.type === 'PAN')?.value || '' })
|
650
|
+
identifiers.find(i => i.type === 'AADHAAR') && aadhaarSchema.parse({ value: identifiers?.find(i => i.type === 'AADHAAR')?.value || '' })
|
651
|
+
} catch (error) {
|
652
|
+
return false
|
653
|
+
}
|
654
|
+
return true
|
655
|
+
})()}>
|
656
|
+
<div className='flex flex-col items-center w-full gap-4'>
|
657
|
+
<AnimatedButton
|
658
|
+
onClick={handleDiscoverAccount}
|
659
|
+
size='lg'
|
660
|
+
className='w-full h-[50px]'
|
661
|
+
>
|
662
|
+
{t('discoverAccounts')}
|
663
|
+
</AnimatedButton>
|
664
|
+
<AnimatedButton
|
665
|
+
onClick={handleCancel}
|
666
|
+
variant={'ghost'}
|
667
|
+
size='lg'
|
668
|
+
className='text-primary'
|
669
|
+
>
|
670
|
+
{t('cancel')}
|
671
|
+
</AnimatedButton>
|
672
|
+
</div>
|
673
|
+
</MobileFooter>
|
674
|
+
|
675
|
+
<WebFooter show={identifiers?.filter(i => !i.value || i.value === '').length === 0 && (() => {
|
676
|
+
try {
|
677
|
+
identifiers.find(i => i.type === 'PAN') && panSchema.parse({ value: identifiers?.find(i => i.type === 'PAN')?.value || '' })
|
678
|
+
identifiers.find(i => i.type === 'AADHAAR') && aadhaarSchema.parse({ value: identifiers?.find(i => i.type === 'AADHAAR')?.value || '' })
|
679
|
+
} catch (error) {
|
680
|
+
return false
|
681
|
+
}
|
682
|
+
return true
|
683
|
+
})()}>
|
684
|
+
<AnimatedButton
|
685
|
+
onClick={handleCancel}
|
686
|
+
variant={'ghost'}
|
687
|
+
size='lg'
|
688
|
+
className='text-primary'
|
689
|
+
>
|
690
|
+
{t('cancel')}
|
691
|
+
</AnimatedButton>
|
692
|
+
<AnimatedButton onClick={handleDiscoverAccount} size='lg'>
|
693
|
+
{t('discoverAccounts')}
|
694
|
+
</AnimatedButton>
|
695
|
+
</WebFooter>
|
696
|
+
|
697
|
+
|
698
|
+
|
699
|
+
<BottomSheet
|
700
|
+
isOpen={isMobile && showModal.isOpen}
|
701
|
+
onClose={() => setShowModal(prev => ({ ...prev, isOpen: false, error: '' }))}
|
702
|
+
title={showModal.title}
|
703
|
+
height='auto'
|
704
|
+
showHandle={true}
|
705
|
+
showCloseButton={false}
|
706
|
+
>
|
707
|
+
{showModal.content == 'otp' ? <div className='flex flex-col items-center gap-4'>
|
708
|
+
<OTPInputComponent
|
709
|
+
maxLength={4}
|
710
|
+
title=''
|
711
|
+
editable={true}
|
712
|
+
mobileNumber={showModal.number}
|
713
|
+
onResend={() => {
|
714
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
715
|
+
addNewNoQuery.mutate(undefined, {
|
716
|
+
onSuccess: () => {
|
717
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
718
|
+
},
|
719
|
+
onError: (error: Error) => {
|
720
|
+
setShowModal(old => ({ ...old, otp: null, error: error.response?.data?.errorMsg || 'Something went wrong' }))
|
721
|
+
// console.error(error)
|
722
|
+
}
|
723
|
+
})
|
724
|
+
}}
|
725
|
+
countdownTime={18}
|
726
|
+
error={Boolean(showModal.error)}
|
727
|
+
errorMessage={showModal.error}
|
728
|
+
onChange={otp => {
|
729
|
+
setShowModal(old => ({ ...old, otp, error: '' }))
|
730
|
+
}}
|
731
|
+
handleEdit={() => {
|
732
|
+
setShowModal(old => ({ ...old, otp: null, error: '' }))
|
733
|
+
openAddNumberModal(showModal.number)
|
734
|
+
}}
|
735
|
+
/>
|
736
|
+
<AnimatedButton
|
737
|
+
className='w-full h-11'
|
738
|
+
onClick={() => {
|
739
|
+
verifyNewNoQuery.mutate()
|
740
|
+
}}
|
741
|
+
>
|
742
|
+
Next
|
743
|
+
</AnimatedButton>
|
744
|
+
</div> : showModal.content}
|
745
|
+
</BottomSheet>
|
746
|
+
|
747
|
+
</div >
|
748
|
+
)
|
749
|
+
}
|
750
|
+
|
751
|
+
export default DiscoverAccount
|