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,836 @@
|
|
1
|
+
import BankCard from '@/components/cards/BankCard'
|
2
|
+
import SectionTitle from '@/components/title/SectionTitle'
|
3
|
+
import { AnimatedButton } from '@/components/ui/animatedButton'
|
4
|
+
import { Checkbox } from '@/components/ui/checkbox'
|
5
|
+
import {
|
6
|
+
Collapsible,
|
7
|
+
CollapsibleContent,
|
8
|
+
CollapsibleTrigger
|
9
|
+
} from '@/components/ui/collapsible'
|
10
|
+
import { Label } from '@/components/ui/label'
|
11
|
+
import { MobileFooter } from '@/components/ui/mobile-footer'
|
12
|
+
import WebFooter from '@/components/ui/web-footer'
|
13
|
+
import {
|
14
|
+
BriefcaseBusiness,
|
15
|
+
ChevronDown,
|
16
|
+
ChevronUp,
|
17
|
+
CircleGauge,
|
18
|
+
Crosshair,
|
19
|
+
Edit2,
|
20
|
+
FileText,
|
21
|
+
Info,
|
22
|
+
InfoIcon,
|
23
|
+
PanelTop,
|
24
|
+
SquareActivity
|
25
|
+
} from 'lucide-react'
|
26
|
+
import React, { useEffect, useState } from 'react'
|
27
|
+
import { trackEvent, EVENTS } from '@/utils/posthog'
|
28
|
+
import { FrostedLayout } from '@/components/layouts/FrostedLayout'
|
29
|
+
import { useRedirectStore } from '@/store/redirect.store'
|
30
|
+
import {
|
31
|
+
consentService,
|
32
|
+
LinkedAccount,
|
33
|
+
ConsentDetails,
|
34
|
+
ApprovalRequest
|
35
|
+
} from '@/services/api'
|
36
|
+
import { useNavigate } from 'react-router-dom'
|
37
|
+
import logo from '../../assets/brand/saafe-logo.svg'
|
38
|
+
import Modal from '@/components/ui/modal'
|
39
|
+
import { useMediaQuery } from '@/hooks/use-media-query'
|
40
|
+
import { useTranslation } from 'react-i18next'
|
41
|
+
import { Alert, AlertTitle } from '@/components/ui/alert'
|
42
|
+
import { useFipStore } from '@/store/fip.store'
|
43
|
+
import { useRTL } from '@/contexts/RTLContext'
|
44
|
+
import { useSetPageTitle } from '@/hooks/use-page-title'
|
45
|
+
import DummyFooter from '@/components/dummyFooter'
|
46
|
+
import { useMandatoryConsentStore } from '@/store/mandatoryConsent.store'
|
47
|
+
import { handleApiErrorWithCallback } from '@/utils/error-callback'
|
48
|
+
|
49
|
+
type ConsentDetail = {
|
50
|
+
id: number | string
|
51
|
+
modal: boolean
|
52
|
+
}
|
53
|
+
|
54
|
+
type GroupedAccounts = {
|
55
|
+
[key: string]: LinkedAccount[]
|
56
|
+
}
|
57
|
+
|
58
|
+
// Add constants for S3 bucket URL
|
59
|
+
const S3_LOGO_BASE_URL = import.meta.env.VITE_IMAGE_BASE_URL
|
60
|
+
|
61
|
+
// Define an interface for DataConsumer
|
62
|
+
interface DataConsumer {
|
63
|
+
id?: string;
|
64
|
+
name?: string;
|
65
|
+
}
|
66
|
+
|
67
|
+
const ReviewConsent = () => {
|
68
|
+
const navigate = useNavigate()
|
69
|
+
const { decodedInfo } = useRedirectStore()
|
70
|
+
const consentHandle = decodedInfo?.srcref || ''
|
71
|
+
const fiuId = decodedInfo?.fiuId
|
72
|
+
const isMobile = useMediaQuery('(max-width: 768px)')
|
73
|
+
const { t } = useTranslation()
|
74
|
+
const { mandatoryConsent, setMandatoryConsent } = useMandatoryConsentStore()
|
75
|
+
|
76
|
+
const ACCOUNT_TYPE_LABELS_TRANSLATED: { [key: string]: string } = {
|
77
|
+
BANK_ACCOUNT: t('consent.consentTypes.BANK_ACCOUNT'),
|
78
|
+
MUTUAL_FUNDS: t('consent.consentTypes.MUTUAL_FUNDS'),
|
79
|
+
GST: t('consent.consentTypes.GST')
|
80
|
+
}
|
81
|
+
|
82
|
+
const [open, setOpen] = React.useState(false)
|
83
|
+
const [consentDetailsCollapse, setConsentDetailsCollapse] =
|
84
|
+
useState<ConsentDetail>({ id: 0, modal: false })
|
85
|
+
|
86
|
+
const [linkedAccounts, setLinkedAccounts] = useState<LinkedAccount[]>([])
|
87
|
+
const [consentDetails, setConsentDetails] = useState<ConsentDetails[]>([])
|
88
|
+
const [selectedConsent, setSelectedConsent] = useState<ConsentDetails[]>([])
|
89
|
+
const [loading, setLoading] = useState(true)
|
90
|
+
const [error, setError] = useState<string | null>(null)
|
91
|
+
const [groupedAccounts, setGroupedAccounts] = useState<GroupedAccounts>({})
|
92
|
+
const [showRejectModal, setShowRejectModal] = useState(false)
|
93
|
+
const { activeCategory, accountForConsent } = useFipStore()
|
94
|
+
const { isRTL } = useRTL()
|
95
|
+
|
96
|
+
// page title
|
97
|
+
useSetPageTitle(consentDetails.length > 1 ? 'Review consents' : 'Review consent');
|
98
|
+
|
99
|
+
// Fetch mandatory consent data
|
100
|
+
useEffect(() => {
|
101
|
+
const fetchMandatoryConsent = async () => {
|
102
|
+
if (!fiuId) {
|
103
|
+
// console.warn('No FIU ID available, using default: pentafox-fiu');
|
104
|
+
}
|
105
|
+
|
106
|
+
try {
|
107
|
+
// Use the correct fiuId from decodedInfo
|
108
|
+
const fiuIdToUse = fiuId || 'pentafox-fiu';
|
109
|
+
|
110
|
+
const mandatoryConsentData = await consentService.getMandatoryConsent(fiuIdToUse);
|
111
|
+
setMandatoryConsent(mandatoryConsentData);
|
112
|
+
} catch (err) {
|
113
|
+
// console.error('Error fetching mandatory consent data:', err);
|
114
|
+
handleApiErrorWithCallback(err, 'Failed to fetch mandatory consent data', consentHandle, decodedInfo?.redirect);
|
115
|
+
// Continue even if this fails, we'll use default values
|
116
|
+
}
|
117
|
+
};
|
118
|
+
|
119
|
+
fetchMandatoryConsent();
|
120
|
+
}, [fiuId, setMandatoryConsent]);
|
121
|
+
|
122
|
+
// Fetch data on component mount
|
123
|
+
useEffect(() => {
|
124
|
+
if (!consentHandle) {
|
125
|
+
setError('Consent handle not found')
|
126
|
+
setLoading(false)
|
127
|
+
return
|
128
|
+
}
|
129
|
+
|
130
|
+
const fetchData = async () => {
|
131
|
+
try {
|
132
|
+
const fipId = decodedInfo?.fipId;
|
133
|
+
const [accountsData, consentData] = await Promise.all([
|
134
|
+
consentService.getLinkedAccounts(consentHandle, fipId),
|
135
|
+
consentService.getConsentDetails(consentHandle)
|
136
|
+
])
|
137
|
+
const result = accountForConsent?.length > 0 ? accountsData.filter(account => accountForConsent.includes(account.linkRefNumber)) : accountsData
|
138
|
+
|
139
|
+
setLinkedAccounts(result)
|
140
|
+
setConsentDetails(consentData || []) // Store all consent details
|
141
|
+
|
142
|
+
|
143
|
+
// Group accounts by fiType
|
144
|
+
const grouped = result.reduce(
|
145
|
+
(groups: GroupedAccounts, account) => {
|
146
|
+
const fiType = account.fiType
|
147
|
+
if (!groups[fiType]) {
|
148
|
+
groups[fiType] = []
|
149
|
+
}
|
150
|
+
groups[fiType].push(account)
|
151
|
+
return groups
|
152
|
+
},
|
153
|
+
{}
|
154
|
+
)
|
155
|
+
|
156
|
+
setGroupedAccounts(grouped)
|
157
|
+
|
158
|
+
// Select consents based on mandatory consent settings
|
159
|
+
const initialSelectedConsents: ConsentDetails[] = []
|
160
|
+
consentData.forEach(consent => {
|
161
|
+
const purposeCode = consent.Purpose?.code
|
162
|
+
if (purposeCode && mandatoryConsent[purposeCode]) {
|
163
|
+
const { enabled } = mandatoryConsent[purposeCode]
|
164
|
+
if (enabled) {
|
165
|
+
initialSelectedConsents.push(consent)
|
166
|
+
}
|
167
|
+
}
|
168
|
+
})
|
169
|
+
|
170
|
+
if (consentData?.length == 1) {
|
171
|
+
setSelectedConsent([consentData[0]])
|
172
|
+
} else {
|
173
|
+
setSelectedConsent(initialSelectedConsents)
|
174
|
+
}
|
175
|
+
|
176
|
+
|
177
|
+
setLoading(false)
|
178
|
+
} catch (err) {
|
179
|
+
// console.error('Error fetching data:', err)
|
180
|
+
const errorMessage = 'Failed to load data. Please try again.';
|
181
|
+
setError(errorMessage);
|
182
|
+
setLoading(false);
|
183
|
+
handleApiErrorWithCallback(err, errorMessage, consentHandle, decodedInfo?.redirect);
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
fetchData()
|
188
|
+
}, [consentHandle, mandatoryConsent, accountForConsent])
|
189
|
+
|
190
|
+
const handleViewMore = (id: string) => {
|
191
|
+
setConsentDetailsCollapse({ id, modal: !consentDetailsCollapse.modal })
|
192
|
+
trackEvent(EVENTS.VIEW_MORE_CONSENT, { consentId: id })
|
193
|
+
}
|
194
|
+
|
195
|
+
const toggleConsentSelection = (consentHandle: string) => {
|
196
|
+
const consent = consentDetails.find(c => c.ConsentHandle === consentHandle)
|
197
|
+
if (!consent) return
|
198
|
+
|
199
|
+
// Check if the consent is mandatory
|
200
|
+
const purposeCode = consent.Purpose?.code
|
201
|
+
if (purposeCode && mandatoryConsent[purposeCode]?.mandatory) {
|
202
|
+
// If mandatory, don't allow deselection
|
203
|
+
return
|
204
|
+
}
|
205
|
+
|
206
|
+
const isSelected = selectedConsent.some(c => c.ConsentHandle === consentHandle)
|
207
|
+
|
208
|
+
if (isSelected) {
|
209
|
+
setSelectedConsent(selectedConsent.filter(c => c.ConsentHandle !== consentHandle))
|
210
|
+
} else {
|
211
|
+
setSelectedConsent([...selectedConsent, consent])
|
212
|
+
}
|
213
|
+
}
|
214
|
+
useEffect(() => {
|
215
|
+
if (consentDetails?.length) {
|
216
|
+
if (consentDetails?.length > 1) {
|
217
|
+
const result = []
|
218
|
+
consentDetails.forEach((i) => {
|
219
|
+
const isMandatory = Boolean(mandatoryConsent[i?.Purpose?.code]?.mandatory || mandatoryConsent[i?.Purpose?.code]?.enabled)
|
220
|
+
if (isMandatory) result.push(i)
|
221
|
+
})
|
222
|
+
setSelectedConsent(result)
|
223
|
+
} else {
|
224
|
+
setSelectedConsent([consentDetails[0]])
|
225
|
+
}
|
226
|
+
}
|
227
|
+
}, [consentDetails])
|
228
|
+
|
229
|
+
const handleSelectAll = () => {
|
230
|
+
const allSelected = consentDetails.length === selectedConsent.length
|
231
|
+
|
232
|
+
if (allSelected) {
|
233
|
+
// When deselecting all, keep mandatory consents selected
|
234
|
+
const mandatoryConsents = consentDetails.filter(consent => {
|
235
|
+
const purposeCode = consent.Purpose?.code
|
236
|
+
return purposeCode && mandatoryConsent[purposeCode]?.mandatory
|
237
|
+
})
|
238
|
+
|
239
|
+
setSelectedConsent(mandatoryConsents)
|
240
|
+
} else {
|
241
|
+
setSelectedConsent([...consentDetails])
|
242
|
+
|
243
|
+
}
|
244
|
+
|
245
|
+
// Create state with all consents selected or deselected
|
246
|
+
|
247
|
+
// setSelectedConsents(newSelectedState)
|
248
|
+
trackEvent(EVENTS.SELECT_ALL_ACCOUNTS)
|
249
|
+
}
|
250
|
+
|
251
|
+
const getSelectedConsents = () => {
|
252
|
+
return selectedConsent || []
|
253
|
+
}
|
254
|
+
|
255
|
+
const handleApproveConsent = async () => {
|
256
|
+
trackEvent(EVENTS.APPROVE_CONSENT)
|
257
|
+
const selectedConsentsList = getSelectedConsents()
|
258
|
+
|
259
|
+
if (selectedConsentsList.length === 0) {
|
260
|
+
setError('Please select at least one consent')
|
261
|
+
return
|
262
|
+
}
|
263
|
+
|
264
|
+
try {
|
265
|
+
setLoading(true)
|
266
|
+
|
267
|
+
const result = accountForConsent?.length > 0 ? linkedAccounts.filter(account => accountForConsent.includes(account.linkRefNumber)) : linkedAccounts
|
268
|
+
|
269
|
+
// Prepare accounts to be associated with selected consents
|
270
|
+
const accountsToApprove = result.map(account => ({
|
271
|
+
linkRefNumber: account.linkRefNumber,
|
272
|
+
fipHandle: account.fipHandle
|
273
|
+
}))
|
274
|
+
|
275
|
+
const approvalData: ApprovalRequest = {
|
276
|
+
consentHandle: selectedConsentsList.map(consent => consent.ConsentHandle),
|
277
|
+
constentApprovalStatus: 'APPROVED',
|
278
|
+
accounts: accountsToApprove
|
279
|
+
}
|
280
|
+
|
281
|
+
const consentResult = await consentService.submitConsentVerification(approvalData)
|
282
|
+
console.log("navigatig")
|
283
|
+
|
284
|
+
if (consentResult?.length && consentResult?.find(item => item.ConsentStatus === 'FAILED')) {
|
285
|
+
navigate('/rejected', { state: { consentResult } })
|
286
|
+
return
|
287
|
+
} else if (consentResult?.length) {
|
288
|
+
navigate('/success')
|
289
|
+
} else {
|
290
|
+
setError('Failed to approve consent. Please try again.')
|
291
|
+
}
|
292
|
+
|
293
|
+
// if (result.status === 200) {
|
294
|
+
// } else {
|
295
|
+
// setError('Failed to approve consent. Please try again.')
|
296
|
+
// }
|
297
|
+
|
298
|
+
} catch (err) {
|
299
|
+
// console.error('Error approving consent:', err)
|
300
|
+
const errorMessage = 'Failed to approve consent. Please try again.';
|
301
|
+
setError(errorMessage);
|
302
|
+
handleApiErrorWithCallback(err, errorMessage, consentHandle, decodedInfo?.redirect);
|
303
|
+
} finally {
|
304
|
+
setLoading(false)
|
305
|
+
// navigate('/success')<div className="flex flex-col items-center gap-6 mt-10">
|
306
|
+
{/* Download section */ }
|
307
|
+
|
308
|
+
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
const handleRejectConsent = async () => {
|
313
|
+
trackEvent(EVENTS.REJECT_CONSENT)
|
314
|
+
|
315
|
+
try {
|
316
|
+
setLoading(true)
|
317
|
+
|
318
|
+
// Include all accounts in rejection
|
319
|
+
const accountsToReject = linkedAccounts.map(account => ({
|
320
|
+
linkRefNumber: account.linkRefNumber,
|
321
|
+
fipHandle: account.fipHandle
|
322
|
+
}))
|
323
|
+
|
324
|
+
const rejectionData: ApprovalRequest = {
|
325
|
+
consentHandle: [consentHandle],
|
326
|
+
constentApprovalStatus: 'REJECTED',
|
327
|
+
accounts: accountsToReject
|
328
|
+
}
|
329
|
+
|
330
|
+
await consentService.submitConsentVerification(rejectionData)
|
331
|
+
|
332
|
+
navigate('/rejected')
|
333
|
+
} catch (err) {
|
334
|
+
// console.error('Error rejecting consent:', err)
|
335
|
+
const errorMessage = 'Failed to reject consent. Please try again.';
|
336
|
+
setError(errorMessage);
|
337
|
+
handleApiErrorWithCallback(err, errorMessage, consentHandle, decodedInfo?.redirect);
|
338
|
+
} finally {
|
339
|
+
setLoading(false)
|
340
|
+
}
|
341
|
+
}
|
342
|
+
const getFrequencyText = (consent: ConsentDetails) => {
|
343
|
+
if (!consent?.Frequency) return 'Onetime'
|
344
|
+
|
345
|
+
const { unit, value } = consent.Frequency
|
346
|
+
|
347
|
+
if (!value || value === 0) return 'Onetime'
|
348
|
+
|
349
|
+
const formattedUnit = unit.toLowerCase()
|
350
|
+
// const pluralizedUnit = value > 1 ? `${formattedUnit}s` : formattedUnit
|
351
|
+
|
352
|
+
return `${value} per ${formattedUnit}`
|
353
|
+
}
|
354
|
+
|
355
|
+
// Get date range text
|
356
|
+
const getDataRangeText = (consent: ConsentDetails) => {
|
357
|
+
if (!consent?.FIDataRange) return ''
|
358
|
+
|
359
|
+
const { from, to } = consent.FIDataRange
|
360
|
+
const fromDate = new Date(from)
|
361
|
+
const toDate = new Date(to)
|
362
|
+
|
363
|
+
const formatDate = (date: Date) => {
|
364
|
+
return date.toLocaleDateString('en-US', {
|
365
|
+
day: '2-digit',
|
366
|
+
month: 'short',
|
367
|
+
year: 'numeric'
|
368
|
+
})
|
369
|
+
}
|
370
|
+
|
371
|
+
return `${formatDate(fromDate)} to ${formatDate(toDate)}`
|
372
|
+
}
|
373
|
+
|
374
|
+
// Check if a consent is mandatory
|
375
|
+
const isConsentMandatory = (consent: ConsentDetails): boolean => {
|
376
|
+
const purposeCode = consent.Purpose?.code
|
377
|
+
return purposeCode ? Boolean(mandatoryConsent[purposeCode]?.mandatory) : false
|
378
|
+
}
|
379
|
+
|
380
|
+
// Function to get logo URL for data consumer
|
381
|
+
const getDataConsumerLogoUrl = (dataConsumer: DataConsumer | undefined) => {
|
382
|
+
if (!dataConsumer || !dataConsumer.id) return logo
|
383
|
+
return `${S3_LOGO_BASE_URL}${dataConsumer.id}.jpeg`
|
384
|
+
}
|
385
|
+
|
386
|
+
if (loading) {
|
387
|
+
return (
|
388
|
+
<FrostedLayout>
|
389
|
+
<div className='flex items-center justify-center h-full'>
|
390
|
+
<p>Loading...</p>
|
391
|
+
</div>
|
392
|
+
</FrostedLayout>
|
393
|
+
)
|
394
|
+
}
|
395
|
+
|
396
|
+
if (error) {
|
397
|
+
return (
|
398
|
+
<FrostedLayout>
|
399
|
+
<div className='flex flex-col items-center justify-center h-full'>
|
400
|
+
<p className='text-red-500'>{error}</p>
|
401
|
+
<AnimatedButton
|
402
|
+
onClick={() => window.location.reload()}
|
403
|
+
className='mt-4'
|
404
|
+
>
|
405
|
+
Try Again
|
406
|
+
</AnimatedButton>
|
407
|
+
</div>
|
408
|
+
</FrostedLayout>
|
409
|
+
)
|
410
|
+
}
|
411
|
+
const purposeCodeMap: Record<string, string> = {
|
412
|
+
"101": "Wealth management service",
|
413
|
+
"102": "Customer spending patterns, budget or other reportings",
|
414
|
+
"103": "For loan underwriting",
|
415
|
+
"104": "For loan monitoring",
|
416
|
+
"105": "For account verification",
|
417
|
+
// Add more codes as needed
|
418
|
+
}
|
419
|
+
|
420
|
+
return (
|
421
|
+
<FrostedLayout>
|
422
|
+
<div className='flex flex-col w-full h-full py-4 md:px-14 sm:px4 mb-14'>
|
423
|
+
{linkedAccounts.length === 0 ?
|
424
|
+
<div className='mb-8'>
|
425
|
+
<Alert>
|
426
|
+
<AlertTitle className='flex items-center justify-between gap-4'>
|
427
|
+
<div className='flex items-center gap-4'>
|
428
|
+
<div className='flex items-center justify-center bg-yellow-600 p-2 rounded-full w-fit'>
|
429
|
+
<Info
|
430
|
+
strokeWidth='2.3'
|
431
|
+
className='h-[18px] w-[18px] text-white'
|
432
|
+
/>
|
433
|
+
</div>
|
434
|
+
<p className='text-lg font-medium'>
|
435
|
+
No accounts selected! You need to select accounts to approve this consent
|
436
|
+
</p>
|
437
|
+
</div>
|
438
|
+
<AnimatedButton
|
439
|
+
variant={'outline'}
|
440
|
+
size={'lg'}
|
441
|
+
className='text-yellow-600 border-yellow-600 border-2 hover:bg-yellow-200 hover:text-yellow-900'
|
442
|
+
onClick={() => { trackEvent(EVENTS.DISCOVER_MORE); navigate(`/link-accounts/${activeCategory}`) }}
|
443
|
+
>
|
444
|
+
Add accounts
|
445
|
+
</AnimatedButton>
|
446
|
+
</AlertTitle>
|
447
|
+
</Alert>
|
448
|
+
</div>
|
449
|
+
: null}
|
450
|
+
{linkedAccounts.length === 0 ? (null
|
451
|
+
) : (
|
452
|
+
<>
|
453
|
+
<SectionTitle title={linkedAccounts.length === 1 ? t('keywords.account') : t('keywords.accounts')} />
|
454
|
+
<div className='bg-white dark:bg-card rounded-md mt-4 p-4 border-1 border-gray-100 dark:border-gray-800'>
|
455
|
+
<Collapsible open={open} onOpenChange={setOpen}>
|
456
|
+
<CollapsibleTrigger asChild>
|
457
|
+
<div className='flex items-center justify-between gap-2 cursor-pointer'>
|
458
|
+
<div className='flex items-center justify-center gap-4 font-medium relative '>
|
459
|
+
<div className='w-12 h-8'>
|
460
|
+
{linkedAccounts.length > 0 && (
|
461
|
+
<img
|
462
|
+
src={linkedAccounts[0].logoUrl}
|
463
|
+
className='absolute w-8 h-8 rounded-md'
|
464
|
+
alt='First account logo'
|
465
|
+
/>
|
466
|
+
)}
|
467
|
+
{linkedAccounts.length > 1 && (
|
468
|
+
<img
|
469
|
+
src={linkedAccounts[1].logoUrl}
|
470
|
+
className={`absolute w-8 h-8 left-4 ${isRTL ? 'right-4' : 'left-4'} rounded-md`}
|
471
|
+
alt='Second account logo'
|
472
|
+
/>
|
473
|
+
)}
|
474
|
+
</div>
|
475
|
+
<p className='text-content-primary dark:text-gray-400 text-md'>
|
476
|
+
{linkedAccounts.length === 1
|
477
|
+
? linkedAccounts[0].fipName
|
478
|
+
: linkedAccounts.length > 1
|
479
|
+
? `${linkedAccounts[0].fipName} ${linkedAccounts.length > 2 ? `+${linkedAccounts.length - 1} more` : ''}`
|
480
|
+
: 'View more'}
|
481
|
+
</p>
|
482
|
+
</div>
|
483
|
+
</div>
|
484
|
+
</CollapsibleTrigger>
|
485
|
+
<CollapsibleContent open={open}>
|
486
|
+
<div className='flex flex-col gap-4 mt-4 border-t-1 border-gray-300 dark:border-gray-600 border-dashed'>
|
487
|
+
{Object.entries(groupedAccounts).map(([fiType, accounts], index) => (
|
488
|
+
<div key={fiType}>
|
489
|
+
<div className='flex items-center justify-between mt-4 mb-4'>
|
490
|
+
<Label className='text-sm md:text-md text-black dark:text-gray-300 font-semibold'>
|
491
|
+
{ACCOUNT_TYPE_LABELS_TRANSLATED[fiType] || fiType}
|
492
|
+
</Label>
|
493
|
+
{index == 0 ? <AnimatedButton
|
494
|
+
variant={'text'}
|
495
|
+
size={'text'}
|
496
|
+
className='text-primary'
|
497
|
+
onClick={() => { trackEvent(EVENTS.EDIT_ACCOUNT); navigate(`/link-accounts/old-user`) }}
|
498
|
+
>
|
499
|
+
<Edit2 /> Edit
|
500
|
+
</AnimatedButton> : null}
|
501
|
+
</div>
|
502
|
+
<div className='flex flex-col gap-4'>
|
503
|
+
{accounts.map(account => (
|
504
|
+
<BankCard
|
505
|
+
key={account.accountRefNumber}
|
506
|
+
bankName={account.fipName}
|
507
|
+
badgeText={false}
|
508
|
+
image={account.logoUrl}
|
509
|
+
subText={`${account.accountType
|
510
|
+
} | ***${account.maskedAccNumber.slice(-4)}`}
|
511
|
+
/>
|
512
|
+
))}
|
513
|
+
</div>
|
514
|
+
</div>
|
515
|
+
))}
|
516
|
+
</div>
|
517
|
+
</CollapsibleContent>
|
518
|
+
</Collapsible>
|
519
|
+
|
520
|
+
</div>
|
521
|
+
</>
|
522
|
+
)}
|
523
|
+
|
524
|
+
{consentDetails.length > 0 && (
|
525
|
+
<>
|
526
|
+
<p className='text-sm text-consent-secondary mt-4'>{decodedInfo?.fiuName} requests your explicit consent to access your financial data</p>
|
527
|
+
<SectionTitle
|
528
|
+
title='Consent details'
|
529
|
+
className='mt-6'
|
530
|
+
rightSection={
|
531
|
+
consentDetails.length > 1 && (
|
532
|
+
<AnimatedButton
|
533
|
+
variant={'text'}
|
534
|
+
size={'text'}
|
535
|
+
className='text-primary'
|
536
|
+
onClick={handleSelectAll}
|
537
|
+
>
|
538
|
+
{selectedConsent?.length === consentDetails.length
|
539
|
+
? 'Deselect All'
|
540
|
+
: 'Select All'}
|
541
|
+
</AnimatedButton>
|
542
|
+
)
|
543
|
+
}
|
544
|
+
/>
|
545
|
+
|
546
|
+
<div>
|
547
|
+
{consentDetails.map((consent) => {
|
548
|
+
const isMandatory = isConsentMandatory(consent)
|
549
|
+
const isSelected = selectedConsent.some(c => c.ConsentHandle === consent.ConsentHandle)
|
550
|
+
// Get the logo URL based on data consumer ID
|
551
|
+
const logoUrl = getDataConsumerLogoUrl(consent.DataConsumer)
|
552
|
+
|
553
|
+
return (
|
554
|
+
<div
|
555
|
+
key={consent.ConsentHandle}
|
556
|
+
className='bg-white dark:bg-card rounded-lg mt-1 p-4 border-1 border-gray-100 dark:border-gray-800 relative'
|
557
|
+
>
|
558
|
+
<BankCard
|
559
|
+
bankName={consent?.DataConsumer?.name || ''}
|
560
|
+
image={logoUrl}
|
561
|
+
className={`${consentDetails.length > 1 ? 'cursor-pointer' : ''} ${isMandatory ? 'opacity-90' : ''}`}
|
562
|
+
selected={isSelected}
|
563
|
+
onClick={() => consentDetails.length > 1 && toggleConsentSelection(consent.ConsentHandle)}
|
564
|
+
subText={false}
|
565
|
+
rightSection={
|
566
|
+
consentDetails.length > 1 ? (
|
567
|
+
<Checkbox
|
568
|
+
checked={isSelected}
|
569
|
+
disabled={isMandatory}
|
570
|
+
/>
|
571
|
+
) : null
|
572
|
+
}
|
573
|
+
/>
|
574
|
+
{consent && (
|
575
|
+
<div className='flex flex-col gap-6 mt-4'>
|
576
|
+
|
577
|
+
<div className={`flex flex-row gap-4 ${isRTL ? 'flex-row-reverse' : ''}`} style={{ flexDirection: 'row' }}>
|
578
|
+
<div className='flex items-center justify-around p-2 bg-primary/20 w-fit rounded-full h-fit'>
|
579
|
+
<FileText className='text-primary' />
|
580
|
+
</div>
|
581
|
+
<div>
|
582
|
+
<Label className={`text-xs md:text-sm w-fit font-medium ${isRTL ? 'text-left' : ''} text-consent-secondary dark:text-gray-400`}>
|
583
|
+
Category & data range
|
584
|
+
</Label>
|
585
|
+
<Label className={`text-sm md:text-md font-semibold text-consent-primary w-fit ${isRTL ? 'text-left' : ''} mt-1 dark:text-gray-300`}>
|
586
|
+
{consent.consentTypes?.join(', ') ||
|
587
|
+
'Profile summary, transactions'}
|
588
|
+
{consent.FIDataRange &&
|
589
|
+
` from ${getDataRangeText(consent)}`}
|
590
|
+
{/* {consent.DataFilter &&
|
591
|
+
consent.DataFilter.length > 0 &&
|
592
|
+
` and transactions ${consent.DataFilter[0].operator} ${consent.DataFilter[0].value}`} */}
|
593
|
+
</Label>
|
594
|
+
</div>
|
595
|
+
</div>
|
596
|
+
|
597
|
+
|
598
|
+
|
599
|
+
<div className={`flex flex-row gap-4 ${isRTL ? 'flex-row-reverse' : ''}`} style={{ flexDirection: 'row' }}>
|
600
|
+
|
601
|
+
<div className='flex items-center justify-around p-2 bg-primary/20 w-fit rounded-full h-fit'>
|
602
|
+
<Crosshair className='text-primary' />
|
603
|
+
</div>
|
604
|
+
<div>
|
605
|
+
<Label className={`text-xs md:text-sm w-fit font-medium ${isRTL ? 'text-left' : ''} text-consent-secondary dark:text-gray-400`}>
|
606
|
+
Purpose
|
607
|
+
</Label>
|
608
|
+
<Label className={`text-sm md:text-md font-semibold text-consent-primary w-fit ${isRTL ? 'text-left' : ''} mt-1 dark:text-gray-300`}>
|
609
|
+
{consent.Purpose?.text || purposeCodeMap[consent.Purpose?.code] || 'To process the loan application'}
|
610
|
+
</Label>
|
611
|
+
</div>
|
612
|
+
|
613
|
+
</div>
|
614
|
+
<div className={`flex flex-row gap-4 ${isRTL ? 'flex-row-reverse' : ''}`} style={{ flexDirection: 'row' }}>
|
615
|
+
<div className='flex items-center justify-around p-2 bg-primary/20 w-fit rounded-full h-fit'>
|
616
|
+
<SquareActivity className='text-primary' />
|
617
|
+
</div>
|
618
|
+
|
619
|
+
<div>
|
620
|
+
<Label className={`text-xs md:text-sm w-fit font-medium ${isRTL ? 'text-left' : ''} text-consent-secondary dark:text-gray-400`}>
|
621
|
+
Frequency
|
622
|
+
</Label>
|
623
|
+
<Label className={`text-sm md:text-md font-semibold text-consent-primary w-fit ${isRTL ? 'text-left' : ''} mt-1 dark:text-gray-300`}>
|
624
|
+
{getFrequencyText(consent)}
|
625
|
+
</Label>
|
626
|
+
</div>
|
627
|
+
{/* <div>
|
628
|
+
<Label className={`text-xs md:text-sm w-fit font-medium ${isRTL ? 'text-left' : ''} text-consent-secondary`}>
|
629
|
+
Frequency
|
630
|
+
</Label>
|
631
|
+
<Label className={`text-sm md:text-md font-semibold text-consent-primary w-fit ${isRTL ? 'text-left' : ''} mt-1`}>
|
632
|
+
{children === '1' || children === '1 Onetime' ? 'Onetime' : children}
|
633
|
+
</Label>
|
634
|
+
</div> */}
|
635
|
+
|
636
|
+
</div>
|
637
|
+
|
638
|
+
<Collapsible
|
639
|
+
open={
|
640
|
+
consentDetailsCollapse.id == consent.ConsentHandle &&
|
641
|
+
consentDetailsCollapse.modal
|
642
|
+
}
|
643
|
+
onOpenChange={() => handleViewMore(consent.ConsentHandle)}
|
644
|
+
>
|
645
|
+
<CollapsibleContent
|
646
|
+
open={
|
647
|
+
consentDetailsCollapse.id === consent.ConsentHandle &&
|
648
|
+
consentDetailsCollapse.modal
|
649
|
+
}
|
650
|
+
>
|
651
|
+
<div className='flex flex-col gap-6 w-full'>
|
652
|
+
<div className={`flex flex-row gap-4 ${isRTL ? 'flex-row-reverse' : ''}`} style={{ flexDirection: 'row' }}>
|
653
|
+
<div className='flex items-center justify-around p-2 bg-primary/20 w-fit rounded-full h-fit'>
|
654
|
+
<PanelTop className='text-primary' />
|
655
|
+
</div>
|
656
|
+
<div>
|
657
|
+
<Label className={`text-xs md:text-sm w-fit font-medium ${isRTL ? 'text-left' : ''} text-consent-secondary dark:text-gray-400`}>
|
658
|
+
Account type
|
659
|
+
</Label>
|
660
|
+
<Label className={`text-sm md:text-md font-semibold text-consent-primary w-fit ${isRTL ? 'text-left' : ''} mt-1 capitalize dark:text-gray-300`}>
|
661
|
+
{consent.fiTypes?.map(
|
662
|
+
word => word?.charAt(0)?.toUpperCase() + word?.slice(1)?.toLowerCase()
|
663
|
+
)?.join(', ')?.replace(/_/g, ' ') ||
|
664
|
+
'Bank accounts'}
|
665
|
+
</Label>
|
666
|
+
</div>
|
667
|
+
</div>
|
668
|
+
<div className={`flex flex-row gap-4 ${isRTL ? 'flex-row-reverse' : ''}`} style={{ flexDirection: 'row' }}>
|
669
|
+
<div className='flex items-center justify-around p-2 bg-primary/20 w-fit rounded-full h-fit'>
|
670
|
+
<CircleGauge className='text-primary' />
|
671
|
+
</div>
|
672
|
+
<div>
|
673
|
+
<Label className={`text-xs md:text-sm w-fit font-medium ${isRTL ? 'text-left' : ''} text-consent-secondary dark:text-gray-400`}>
|
674
|
+
Data life
|
675
|
+
</Label>
|
676
|
+
<Label className={`text-sm md:text-md font-semibold text-consent-primary w-fit ${isRTL ? 'text-left' : ''} mt-1 dark:text-gray-300`}>
|
677
|
+
{consent.DataLife
|
678
|
+
? `${consent.DataLife.value
|
679
|
+
} ${consent.DataLife.unit.toLowerCase()}${consent.DataLife.value > 1 ? 's' : ''
|
680
|
+
}`
|
681
|
+
: '-'}
|
682
|
+
</Label>
|
683
|
+
</div>
|
684
|
+
</div>
|
685
|
+
<div className={`flex flex-row gap-4 ${isRTL ? 'flex-row-reverse' : ''}`} style={{ flexDirection: 'row' }}>
|
686
|
+
<div className='flex items-center justify-around p-2 bg-primary/20 w-fit rounded-full h-fit'>
|
687
|
+
<BriefcaseBusiness className='text-primary' />
|
688
|
+
</div>
|
689
|
+
<div>
|
690
|
+
<Label className={`text-xs md:text-sm w-fit font-medium ${isRTL ? 'text-left' : ''} text-consent-secondary dark:text-gray-400`}>
|
691
|
+
Consent validity
|
692
|
+
</Label>
|
693
|
+
<Label className={`text-sm md:text-md font-semibold text-consent-primary w-fit ${isRTL ? 'text-left' : ''} mt-1 dark:text-gray-300`}>
|
694
|
+
{consent.consentStart &&
|
695
|
+
consent.consentExpiry
|
696
|
+
? `Upto ${new Date(
|
697
|
+
consent.consentExpiry
|
698
|
+
).toLocaleDateString('en-US', {
|
699
|
+
day: '2-digit',
|
700
|
+
month: 'short',
|
701
|
+
year: 'numeric'
|
702
|
+
})}`
|
703
|
+
: '-'}
|
704
|
+
</Label>
|
705
|
+
</div>
|
706
|
+
</div>
|
707
|
+
</div>
|
708
|
+
</CollapsibleContent>
|
709
|
+
<div className='w-full flex items-center justify-center mt-4 md:mt-1'>
|
710
|
+
<CollapsibleTrigger>
|
711
|
+
<div className='flex items-center justify-center gap-2 cursor-pointer text-consent-primary dark:text-gray-400 font-semibold text-sm'>
|
712
|
+
View{' '}
|
713
|
+
{consentDetailsCollapse.id === consent.ConsentHandle &&
|
714
|
+
consentDetailsCollapse.modal
|
715
|
+
? 'Less'
|
716
|
+
: 'More'}
|
717
|
+
{consentDetailsCollapse.id === consent.ConsentHandle &&
|
718
|
+
consentDetailsCollapse.modal ? (
|
719
|
+
<ChevronUp />
|
720
|
+
) : (
|
721
|
+
<ChevronDown />
|
722
|
+
)}
|
723
|
+
</div>
|
724
|
+
</CollapsibleTrigger>
|
725
|
+
</div>
|
726
|
+
</Collapsible>
|
727
|
+
|
728
|
+
</div>
|
729
|
+
|
730
|
+
)}
|
731
|
+
</div>
|
732
|
+
|
733
|
+
)
|
734
|
+
})
|
735
|
+
}
|
736
|
+
|
737
|
+
|
738
|
+
|
739
|
+
|
740
|
+
</div>
|
741
|
+
</>
|
742
|
+
)}
|
743
|
+
|
744
|
+
<DummyFooter />
|
745
|
+
</div>
|
746
|
+
|
747
|
+
<MobileFooter show={isMobile && (accountForConsent?.length > 0 ? Boolean(linkedAccounts.filter(account => accountForConsent.includes(account.linkRefNumber))?.length) : Boolean(linkedAccounts?.length) && Boolean(selectedConsent?.length))}>
|
748
|
+
<div className='flex flex-row items-center w-full gap-4 justify-between'>
|
749
|
+
<AnimatedButton
|
750
|
+
onClick={() => setShowRejectModal(true)}
|
751
|
+
variant={'ghost'}
|
752
|
+
size='lg'
|
753
|
+
className='text-primary'
|
754
|
+
disabled={loading}
|
755
|
+
loading={loading}
|
756
|
+
>
|
757
|
+
Reject
|
758
|
+
</AnimatedButton>
|
759
|
+
<AnimatedButton
|
760
|
+
onClick={handleApproveConsent}
|
761
|
+
size='lg'
|
762
|
+
className='h-[50px] w-[48%]'
|
763
|
+
disabled={
|
764
|
+
loading ||
|
765
|
+
selectedConsent.length === 0
|
766
|
+
}
|
767
|
+
>
|
768
|
+
Approve
|
769
|
+
</AnimatedButton>
|
770
|
+
</div>
|
771
|
+
</MobileFooter>
|
772
|
+
|
773
|
+
<WebFooter show={!isMobile && (accountForConsent?.length > 0 ? Boolean(linkedAccounts.filter(account => accountForConsent.includes(account.linkRefNumber))?.length) : Boolean(linkedAccounts?.length) && Boolean(selectedConsent?.length))}>
|
774
|
+
<AnimatedButton
|
775
|
+
onClick={() => setShowRejectModal(true)}
|
776
|
+
variant={'ghost'}
|
777
|
+
size='lg'
|
778
|
+
className='text-primary'
|
779
|
+
disabled={loading}
|
780
|
+
>
|
781
|
+
Reject
|
782
|
+
</AnimatedButton>
|
783
|
+
<AnimatedButton
|
784
|
+
onClick={handleApproveConsent}
|
785
|
+
size='lg'
|
786
|
+
disabled={
|
787
|
+
loading ||
|
788
|
+
selectedConsent.length === 0
|
789
|
+
}
|
790
|
+
>
|
791
|
+
Approve
|
792
|
+
</AnimatedButton>
|
793
|
+
</WebFooter>
|
794
|
+
|
795
|
+
<Modal
|
796
|
+
open={showRejectModal}
|
797
|
+
onOpenChange={open => setShowRejectModal(open)}
|
798
|
+
withCloseIcon={false}
|
799
|
+
>
|
800
|
+
<div className='flex flex-col gap-4 items-center'>
|
801
|
+
<div className='flex items-center justify-center bg-red-100 p-2.5 rounded-full'>
|
802
|
+
<InfoIcon className='h-[28px] w-[28px] text-red-700' />
|
803
|
+
</div>
|
804
|
+
<div className='flex flex-col gap-2 items-center text-center mt-2'>
|
805
|
+
<p className='text-xl font-semibold dark:text-gray-300'>
|
806
|
+
Are you sure you want to reject?
|
807
|
+
</p>
|
808
|
+
<p className='text-sm text-muted-secondary'>
|
809
|
+
Rejecting this consent is a permanent action and cannot be undone.
|
810
|
+
However, you can still view the consent details anytime using
|
811
|
+
Saafe app.
|
812
|
+
</p>
|
813
|
+
</div>
|
814
|
+
<div className='flex gap-2 w-full items-center justify-center'>
|
815
|
+
<AnimatedButton size='lg' onClick={() => setShowRejectModal(false)}>
|
816
|
+
No, go back!
|
817
|
+
</AnimatedButton>
|
818
|
+
<AnimatedButton
|
819
|
+
size='lg'
|
820
|
+
variant='ghost'
|
821
|
+
className='hover:bg-red-100 hover:text-red-700 text-red-700 dark:text-red-500 '
|
822
|
+
onClick={handleRejectConsent}
|
823
|
+
loading={loading}
|
824
|
+
>
|
825
|
+
Yes, Reject
|
826
|
+
</AnimatedButton>
|
827
|
+
</div>
|
828
|
+
</div>
|
829
|
+
</Modal>
|
830
|
+
|
831
|
+
</FrostedLayout>
|
832
|
+
|
833
|
+
)
|
834
|
+
}
|
835
|
+
|
836
|
+
export default ReviewConsent
|