saafe-redirection-flow 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.github/workflows/build-and-deploy.yml +41 -0
  2. package/.gitlab-ci.yml +108 -0
  3. package/.releaserc.json +18 -0
  4. package/.storybook/main.ts +28 -0
  5. package/.storybook/preview.ts +16 -0
  6. package/.storybook/vitest.setup.ts +9 -0
  7. package/.vite/deps/@radix-ui_react-avatar.js +230 -0
  8. package/.vite/deps/@radix-ui_react-avatar.js.map +7 -0
  9. package/.vite/deps/@radix-ui_react-slot.js +12 -0
  10. package/.vite/deps/@radix-ui_react-slot.js.map +7 -0
  11. package/.vite/deps/_metadata.json +79 -0
  12. package/.vite/deps/chunk-5VGQBUCU.js +597 -0
  13. package/.vite/deps/chunk-5VGQBUCU.js.map +7 -0
  14. package/.vite/deps/chunk-DC5AMYBS.js +38 -0
  15. package/.vite/deps/chunk-DC5AMYBS.js.map +7 -0
  16. package/.vite/deps/chunk-HUIEPYH7.js +11265 -0
  17. package/.vite/deps/chunk-HUIEPYH7.js.map +7 -0
  18. package/.vite/deps/chunk-TKHB4QMX.js +281 -0
  19. package/.vite/deps/chunk-TKHB4QMX.js.map +7 -0
  20. package/.vite/deps/chunk-YLDSBLSF.js +1139 -0
  21. package/.vite/deps/chunk-YLDSBLSF.js.map +7 -0
  22. package/.vite/deps/class-variance-authority.js +63 -0
  23. package/.vite/deps/class-variance-authority.js.map +7 -0
  24. package/.vite/deps/lucide-react.js +36984 -0
  25. package/.vite/deps/lucide-react.js.map +7 -0
  26. package/.vite/deps/package.json +3 -0
  27. package/.vite/deps/react-dom_client.js +17917 -0
  28. package/.vite/deps/react-dom_client.js.map +7 -0
  29. package/.vite/deps/react-router-dom.js +452 -0
  30. package/.vite/deps/react-router-dom.js.map +7 -0
  31. package/.vite/deps/react-router.js +234 -0
  32. package/.vite/deps/react-router.js.map +7 -0
  33. package/.vite/deps/react.js +5 -0
  34. package/.vite/deps/react.js.map +7 -0
  35. package/.vite/deps/react_jsx-dev-runtime.js +470 -0
  36. package/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  37. package/CHANGELOG.md +420 -0
  38. package/LICENSE +21 -0
  39. package/README.md +129 -0
  40. package/RELEASE_CHEATSHEET.md +93 -0
  41. package/RELEASE_NOTES.md +120 -0
  42. package/components.json +21 -0
  43. package/docs/DEPLOYMENT_WORKFLOW.md +262 -0
  44. package/docs/RELEASE_GUIDE.md +591 -0
  45. package/docs/architecture.md +432 -0
  46. package/docs/components.md +199 -0
  47. package/docs/index.md +69 -0
  48. package/docs/local-release-workflow.md +234 -0
  49. package/docs/routes.md +118 -0
  50. package/docs/sdk-integration.md +325 -0
  51. package/docs/semantic-release.md +124 -0
  52. package/docs/user-flow.md +206 -0
  53. package/eslint.config.js +28 -0
  54. package/index.html +19 -0
  55. package/install.sh +198 -0
  56. package/package.json +115 -0
  57. package/public/images/bank-logo.png +0 -0
  58. package/public/saafe-icon.svg +9 -0
  59. package/src/App.tsx +171 -0
  60. package/src/__tests__/url-parameters.test.ts +82 -0
  61. package/src/assets/brand/applestore.svg +13 -0
  62. package/src/assets/brand/playstore.svg +23 -0
  63. package/src/assets/brand/saafe-color-white-logo.svg +14 -0
  64. package/src/assets/brand/saafe-icon.svg +9 -0
  65. package/src/assets/brand/saafe-logo.svg +18 -0
  66. package/src/assets/icons/check-icon-dark.svg +27 -0
  67. package/src/assets/icons/check-icon.svg +23 -0
  68. package/src/components/ErrorBoundary.tsx +132 -0
  69. package/src/components/alert/alert.tsx +27 -0
  70. package/src/components/auth/AuthGuard.tsx +76 -0
  71. package/src/components/cards/BankCard.stories.tsx +69 -0
  72. package/src/components/cards/BankCard.tsx +227 -0
  73. package/src/components/cards/OuterCard.tsx +109 -0
  74. package/src/components/cards/WrapperCard.tsx +64 -0
  75. package/src/components/documents/PrivacyContent.tsx +1 -0
  76. package/src/components/dummyFooter.tsx +29 -0
  77. package/src/components/icons/github.tsx +12 -0
  78. package/src/components/language/LanguageSwitcher.tsx +44 -0
  79. package/src/components/layouts/FrostedLayout.stories.tsx +42 -0
  80. package/src/components/layouts/FrostedLayout.tsx +333 -0
  81. package/src/components/layouts/MobileLayout.tsx +403 -0
  82. package/src/components/mobile-background.tsx +136 -0
  83. package/src/components/mobileAppDownload.tsx +30 -0
  84. package/src/components/modal/ModalComp.tsx +27 -0
  85. package/src/components/mode-toggle.tsx +36 -0
  86. package/src/components/page-header.tsx +50 -0
  87. package/src/components/session/SessionTimeoutScreen.tsx +134 -0
  88. package/src/components/session/SessionTimer.tsx +173 -0
  89. package/src/components/step-navigation.tsx +87 -0
  90. package/src/components/title/AppBar.stories.tsx +50 -0
  91. package/src/components/title/AppBar.tsx +150 -0
  92. package/src/components/title/SectionTitle.tsx +31 -0
  93. package/src/components/ui/AnimatedButton.module.css +13 -0
  94. package/src/components/ui/alert.tsx +66 -0
  95. package/src/components/ui/animatedButton.tsx +111 -0
  96. package/src/components/ui/avatar.tsx +51 -0
  97. package/src/components/ui/badge.tsx +36 -0
  98. package/src/components/ui/bottom-sheet.tsx +122 -0
  99. package/src/components/ui/button.tsx +59 -0
  100. package/src/components/ui/calendar.tsx +86 -0
  101. package/src/components/ui/card.tsx +92 -0
  102. package/src/components/ui/checkbox.stories.tsx +49 -0
  103. package/src/components/ui/checkbox.tsx +67 -0
  104. package/src/components/ui/collapsible.tsx +45 -0
  105. package/src/components/ui/dialog.tsx +134 -0
  106. package/src/components/ui/document-link.tsx +26 -0
  107. package/src/components/ui/dot-stepper.tsx +57 -0
  108. package/src/components/ui/dropdown-menu.tsx +255 -0
  109. package/src/components/ui/form.tsx +165 -0
  110. package/src/components/ui/frosted-panel.stories.tsx +86 -0
  111. package/src/components/ui/frosted-panel.tsx +276 -0
  112. package/src/components/ui/input.tsx +39 -0
  113. package/src/components/ui/label.stories.tsx +67 -0
  114. package/src/components/ui/label.tsx +23 -0
  115. package/src/components/ui/mobile-footer.tsx +54 -0
  116. package/src/components/ui/modal.tsx +90 -0
  117. package/src/components/ui/otp-input.stories.tsx +62 -0
  118. package/src/components/ui/otp-input.tsx +221 -0
  119. package/src/components/ui/platform-specific-behavior.tsx +28 -0
  120. package/src/components/ui/popover.tsx +46 -0
  121. package/src/components/ui/progress.tsx +103 -0
  122. package/src/components/ui/radio-group.tsx +45 -0
  123. package/src/components/ui/scroll-area.tsx +56 -0
  124. package/src/components/ui/sdk-params-docs.tsx +53 -0
  125. package/src/components/ui/select.tsx +159 -0
  126. package/src/components/ui/separator.tsx +28 -0
  127. package/src/components/ui/sheet.tsx +137 -0
  128. package/src/components/ui/sidebar.tsx +724 -0
  129. package/src/components/ui/skeleton.stories.tsx +50 -0
  130. package/src/components/ui/skeleton.tsx +15 -0
  131. package/src/components/ui/sonner.tsx +23 -0
  132. package/src/components/ui/step.stories.tsx +132 -0
  133. package/src/components/ui/step.tsx +234 -0
  134. package/src/components/ui/stepper-progress.tsx +136 -0
  135. package/src/components/ui/stepper.tsx +259 -0
  136. package/src/components/ui/tabs.tsx +55 -0
  137. package/src/components/ui/tooltip.tsx +61 -0
  138. package/src/components/ui/url-decode-loader.tsx +36 -0
  139. package/src/components/ui/version-display.tsx +104 -0
  140. package/src/components/ui/web-footer.tsx +36 -0
  141. package/src/config/environments.ts +99 -0
  142. package/src/config/urls.ts +53 -0
  143. package/src/const/fiTypeCategoryMap.ts +19 -0
  144. package/src/contexts/LanguageContext.tsx +41 -0
  145. package/src/contexts/RTLContext.tsx +42 -0
  146. package/src/contexts/ThemeContext.tsx +93 -0
  147. package/src/hooks/use-account-discovery.ts +205 -0
  148. package/src/hooks/use-auth-query.ts +141 -0
  149. package/src/hooks/use-fip-query.ts +72 -0
  150. package/src/hooks/use-media-query.ts +32 -0
  151. package/src/hooks/use-mobile.ts +24 -0
  152. package/src/hooks/use-page-title.tsx +48 -0
  153. package/src/hooks/use-platform.ts +52 -0
  154. package/src/hooks/use-trusted-count.ts +21 -0
  155. package/src/hooks/use-url-decode.ts +90 -0
  156. package/src/hooks/useStep.ts +170 -0
  157. package/src/index.css +154 -0
  158. package/src/interfaces/app.interfaces.ts +39 -0
  159. package/src/interfaces/services.interfaces.ts +65 -0
  160. package/src/lib/i18n.ts +68 -0
  161. package/src/lib/utils.ts +6 -0
  162. package/src/locales/en/common.json +167 -0
  163. package/src/locales/hi/common.json +137 -0
  164. package/src/locales/kn/common.json +137 -0
  165. package/src/locales/ml/common.json +137 -0
  166. package/src/locales/ta/common.json +137 -0
  167. package/src/locales/te/common.json +137 -0
  168. package/src/locales/ur/common.json +138 -0
  169. package/src/main.tsx +46 -0
  170. package/src/pages/Login.tsx +363 -0
  171. package/src/pages/accounts/AccountsToProceed.tsx +396 -0
  172. package/src/pages/accounts/Discover.tsx +76 -0
  173. package/src/pages/accounts/DiscoverAccount.tsx +751 -0
  174. package/src/pages/accounts/LinkSelectedAccounts.tsx +638 -0
  175. package/src/pages/accounts/OldUser.tsx +329 -0
  176. package/src/pages/accounts/link-accounts.tsx +913 -0
  177. package/src/pages/consent/ReviewConsent.tsx +836 -0
  178. package/src/pages/consent/rejected.tsx +253 -0
  179. package/src/pages/consent/success.tsx +220 -0
  180. package/src/providers/query-provider.tsx +24 -0
  181. package/src/providers/toast-provider.tsx +26 -0
  182. package/src/services/api/account.service.ts +296 -0
  183. package/src/services/api/auth.service.ts +206 -0
  184. package/src/services/api/axios.ts +138 -0
  185. package/src/services/api/consent.service.ts +142 -0
  186. package/src/services/api/decode.service.ts +53 -0
  187. package/src/services/api/feedback.service.ts +34 -0
  188. package/src/services/api/fip.service.ts +187 -0
  189. package/src/services/api/index.ts +9 -0
  190. package/src/services/api/public.service.ts +18 -0
  191. package/src/services/api.ts +2 -0
  192. package/src/services/postMessage.service.ts +179 -0
  193. package/src/store/NavigationBlockContext.tsx +34 -0
  194. package/src/store/auth.store.ts +79 -0
  195. package/src/store/fip.store.ts +396 -0
  196. package/src/store/mandatoryConsent.store.ts +24 -0
  197. package/src/store/redirect.store.ts +73 -0
  198. package/src/store/step.store.ts +124 -0
  199. package/src/stories/Button.stories.ts +53 -0
  200. package/src/stories/Button.tsx +37 -0
  201. package/src/stories/Configure.mdx +364 -0
  202. package/src/stories/Header.stories.ts +33 -0
  203. package/src/stories/Header.tsx +56 -0
  204. package/src/stories/Page.stories.ts +32 -0
  205. package/src/stories/Page.tsx +73 -0
  206. package/src/stories/button.css +30 -0
  207. package/src/stories/header.css +32 -0
  208. package/src/stories/page.css +68 -0
  209. package/src/styles/rtl-utils.css +90 -0
  210. package/src/styles/rtl.css +105 -0
  211. package/src/utils/api-error.ts +26 -0
  212. package/src/utils/cn.ts +10 -0
  213. package/src/utils/error-callback.ts +116 -0
  214. package/src/utils/formatAccountNumber.ts +9 -0
  215. package/src/utils/handleIdentifiers.ts +90 -0
  216. package/src/utils/posthog.ts +67 -0
  217. package/src/utils/toast-helpers.ts +61 -0
  218. package/src/vite-env.d.ts +1 -0
  219. package/stage-aa-2506251021.zip +0 -0
  220. package/tsconfig.app.json +33 -0
  221. package/tsconfig.json +13 -0
  222. package/tsconfig.node.json +24 -0
  223. package/vite.config.ts +45 -0
  224. package/vitest.shims.d.ts +1 -0
  225. package/vitest.workspace.ts +46 -0
@@ -0,0 +1,24 @@
1
+ import * as React from "react"
2
+
3
+ const MOBILE_BREAKPOINT = 768
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12
+ }
13
+
14
+ // Modern browsers use addEventListener
15
+ mql.addEventListener("change", onChange)
16
+
17
+ // Set initial value
18
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
19
+
20
+ return () => mql.removeEventListener("change", onChange)
21
+ }, [])
22
+
23
+ return !!isMobile
24
+ }
@@ -0,0 +1,48 @@
1
+ import { useState, useEffect, useContext, createContext, ReactNode } from 'react';
2
+
3
+ interface PageTitleContextType {
4
+ pageTitle: string;
5
+ setPageTitle: (title: string) => void;
6
+ }
7
+
8
+ // Create context with default values
9
+ const PageTitleContext = createContext<PageTitleContextType>({
10
+ pageTitle: '',
11
+ setPageTitle: () => {}
12
+ });
13
+
14
+ // Provider component to wrap around App
15
+ export const PageTitleProvider = ({ children }: { children: ReactNode }) => {
16
+ const [pageTitle, setPageTitle] = useState<string>('');
17
+
18
+ return (
19
+ <PageTitleContext.Provider value={{ pageTitle, setPageTitle }}>
20
+ {children}
21
+ </PageTitleContext.Provider>
22
+ );
23
+ };
24
+
25
+ // Custom hook to use the page title context
26
+ export function usePageTitle(): [string, (title: string) => void] {
27
+ const context = useContext(PageTitleContext);
28
+
29
+ if (!context) {
30
+ throw new Error('usePageTitle must be used within a PageTitleProvider');
31
+ }
32
+
33
+ return [context.pageTitle, context.setPageTitle];
34
+ }
35
+
36
+ // Additional hook to set page title with document.title update
37
+ export function useSetPageTitle(title: string): void {
38
+ const [_, setPageTitle] = usePageTitle();
39
+
40
+ useEffect(() => {
41
+ setPageTitle(title);
42
+ document.title = `${title} | Saafe`;
43
+
44
+ return () => {
45
+ document.title = 'Saafe';
46
+ };
47
+ }, [title, setPageTitle]);
48
+ }
@@ -0,0 +1,52 @@
1
+ import { useRedirectStore } from "@/store/redirect.store";
2
+
3
+ /**
4
+ * Supported platform types for SDK integration
5
+ */
6
+ export type PlatformType =
7
+ | "ios"
8
+ | "android"
9
+ | "react-native"
10
+ | "flutter"
11
+ | "web"
12
+ | "unknown";
13
+
14
+ /**
15
+ * Normalized platform values to ensure consistent casing
16
+ */
17
+ const NORMALIZED_PLATFORMS: Record<string, PlatformType> = {
18
+ 'ios': 'ios',
19
+ 'android': 'android',
20
+ 'react-native': 'react-native',
21
+ 'reactnative': 'react-native',
22
+ 'flutter': 'flutter',
23
+ 'web': 'web'
24
+ };
25
+
26
+ /**
27
+ * Hook to detect and provide platform-specific information
28
+ * Usage:
29
+ * const { platform, isMobile, isIOS, isAndroid } = usePlatform();
30
+ */
31
+ export function usePlatform() {
32
+ const { decodedInfo } = useRedirectStore();
33
+
34
+ // Normalize platform value to ensure consistent handling
35
+ const rawPlatform = decodedInfo?.platform?.toLowerCase() || '';
36
+ const platform = NORMALIZED_PLATFORMS[rawPlatform] || "unknown";
37
+
38
+ const isMobile = platform === "ios" || platform === "android";
39
+ const isNativeSDK = platform === "ios" || platform === "android" || platform === "react-native" || platform === "flutter";
40
+ const isWeb = platform === "web" || platform === "unknown";
41
+
42
+ return {
43
+ platform,
44
+ isMobile,
45
+ isNativeSDK,
46
+ isWeb,
47
+ isIOS: platform === "ios",
48
+ isAndroid: platform === "android",
49
+ isReactNative: platform === "react-native",
50
+ isFlutter: platform === "flutter"
51
+ };
52
+ }
@@ -0,0 +1,21 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { publicService } from '@/services/api';
3
+
4
+ /**
5
+ * Hook to fetch trusted user count
6
+ */
7
+ export function useTrustedCount() {
8
+ return useQuery({
9
+ queryKey: ['trusted-count'],
10
+ queryFn: publicService.getTrustedCount,
11
+ staleTime: 5 * 60 * 1000, // 5 minutes
12
+ retry: 2,
13
+ refetchOnWindowFocus: false,
14
+ select: (data) => {
15
+ return {
16
+ ...data,
17
+ displayText: `Trusted by ${data.trustedCount} Indians`
18
+ };
19
+ }
20
+ });
21
+ }
@@ -0,0 +1,90 @@
1
+ import { useMutation } from '@tanstack/react-query'
2
+ import { DecodeUrlParams, decodeService } from '@/services/api/decode.service'
3
+ import { useRedirectStore } from '@/store/redirect.store'
4
+ import { useEffect } from 'react'
5
+
6
+ /**
7
+ * Parse URL search params to extract required decoding parameters
8
+ */
9
+ export function extractUrlParams(): DecodeUrlParams | null {
10
+ // Check if we're in a browser environment
11
+ if (typeof window === 'undefined') return null
12
+
13
+ const urlParams = new URLSearchParams(window.location.search)
14
+ const fi = urlParams.get('fi')
15
+ const ecreq = urlParams.get('ecreq')
16
+ const reqdate = urlParams.get('reqdate')
17
+ const profile = urlParams.get('profile')
18
+ const platform = urlParams.get('platform')
19
+ const theme = urlParams.get('theme')
20
+
21
+ if (!fi || !ecreq || !reqdate) {
22
+ return null
23
+ }
24
+
25
+ return {
26
+ fi,
27
+ ecreq,
28
+ reqdate,
29
+ profile: profile || undefined,
30
+ platform: platform || undefined,
31
+ theme: theme || undefined
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Hook for decoding URL parameters from redirection
37
+ */
38
+ export function useUrlDecode() {
39
+ const { setDecodedInfo, setDecodingError, isDecoded } = useRedirectStore()
40
+
41
+ const decodeMutation = useMutation({
42
+ mutationFn: async (params: DecodeUrlParams) => {
43
+ try {
44
+ const result = await decodeService.decodeUrlParams(params)
45
+
46
+ // Handle specific platform and theme flags
47
+ if (params.platform) {
48
+ result.platform = params.platform;
49
+ }
50
+
51
+ if (params.theme) {
52
+ result.theme = params.theme;
53
+ }
54
+
55
+ // Log platform and theme for debugging
56
+ if (process.env.NODE_ENV !== 'production') {
57
+ console.log('Platform detected:', result.platform || 'none');
58
+ console.log('Theme detected:', result.theme || 'system (default)');
59
+ }
60
+
61
+ setDecodedInfo(result)
62
+ return result
63
+ } catch (error: Error | unknown) {
64
+ // console.error('Error decoding URL params:', error)
65
+ const errorMessage = error instanceof Error ? error.message : 'Failed to decode URL parameters'
66
+ setDecodingError(errorMessage)
67
+ throw error
68
+ }
69
+ }
70
+ })
71
+
72
+ // Automatically decode URL parameters on mount if not already decoded
73
+ useEffect(() => {
74
+ if (!isDecoded) {
75
+ const params = extractUrlParams()
76
+ if (params && decodeMutation.isIdle) {
77
+ decodeMutation.mutate(params)
78
+ }
79
+ }
80
+ }, [isDecoded])
81
+
82
+ return {
83
+ decode: (params: DecodeUrlParams) => decodeMutation.mutate(params),
84
+ isLoading: decodeMutation.isPending,
85
+ isError: decodeMutation.isError,
86
+ error: decodeMutation.error,
87
+ isSuccess: decodeMutation.isSuccess,
88
+ data: decodeMutation.data
89
+ }
90
+ }
@@ -0,0 +1,170 @@
1
+ import { useState, useCallback, useEffect } from 'react'
2
+ import { useLocation, useNavigate } from 'react-router-dom'
3
+
4
+ interface Step {
5
+ step: number
6
+ title: string
7
+ path: string
8
+ }
9
+
10
+ const steps: Step[] = [
11
+ { step: 1, title: 'Login/Signup', path: '/login' },
12
+ { step: 2, title: 'Link Accounts', path: '/link-accounts' },
13
+ // Category paths - all these are child steps of Link Accounts
14
+ { step: 3, title: 'Banks', path: '/link-accounts/banks' },
15
+ { step: 4, title: 'GST', path: '/link-accounts/gst' },
16
+ { step: 5, title: 'Insurance', path: '/link-accounts/insurance' },
17
+ // Flow paths within each category
18
+ { step: 10, title: 'Discovery', path: '/link-accounts/discovery' },
19
+ { step: 11, title: 'Link', path: '/link-accounts/link' },
20
+ {
21
+ step: 12,
22
+ title: 'Discover Account',
23
+ path: '/link-accounts/discover-account'
24
+ },
25
+ // Final step
26
+ { step: 13, title: 'Review Consent', path: '/review' }
27
+ ]
28
+
29
+ interface UseStepProps {
30
+ initialStep?: number
31
+ totalSteps: number
32
+ onStepComplete?: (step: number) => void
33
+ }
34
+
35
+ export const useStep = ({
36
+ initialStep = 1,
37
+ totalSteps,
38
+ onStepComplete
39
+ }: UseStepProps) => {
40
+ const location = useLocation()
41
+ const navigate = useNavigate()
42
+ const [activeStep, setActiveStep] = useState(initialStep)
43
+ const [completedSteps, setCompletedSteps] = useState<number[]>([])
44
+
45
+ // Update active step when route changes
46
+ useEffect(() => {
47
+ const currentPath = location.pathname
48
+ // First check for exact matches
49
+ const exactMatchIndex = steps.findIndex(s => s.path === currentPath)
50
+ let step = 0
51
+ if (exactMatchIndex >= 0) {
52
+ step = steps[exactMatchIndex].step
53
+ } else {
54
+ // Handle nested paths like /link-accounts/banks
55
+ if (currentPath.startsWith('/link-accounts/')) {
56
+ const pathSegment = currentPath.split('/').pop() || ''
57
+
58
+ // Check if this is a category path
59
+ const categoryStep = steps.find(
60
+ s =>
61
+ s.path === `/link-accounts/${pathSegment}` &&
62
+ s.step >= 3 &&
63
+ s.step <= 5
64
+ )
65
+
66
+ if (categoryStep) {
67
+ // It's a category path
68
+ step = categoryStep.step
69
+ } else {
70
+ // It's a flow path (discovery, link, etc.) - set to Link Accounts step
71
+ step = 2
72
+ }
73
+ } else if (currentPath === '/link-accounts') {
74
+ // Base link accounts path
75
+ step = 2
76
+ } else if (currentPath === '/review') {
77
+ // Review page
78
+ step = 13
79
+ } else {
80
+ // Default to step 1
81
+ step = 1
82
+ }
83
+ }
84
+
85
+ if (step > 0 && step <= totalSteps) {
86
+ setActiveStep(step)
87
+
88
+ // Mark previous steps as completed
89
+ if (step > 1) {
90
+ setCompletedSteps(prev => {
91
+ // Add all steps from 1 to current-1 as completed
92
+ const mainSteps = [1]
93
+
94
+ // If we're on a category path (3, 4, 5), mark Link Accounts (2) as completed
95
+ if (step >= 3 && step <= 5) {
96
+ mainSteps.push(2)
97
+ }
98
+
99
+ // If we're on review, mark all main steps as completed
100
+ if (step === 13) {
101
+ mainSteps.push(2, 3, 4, 5)
102
+ }
103
+
104
+ return [...new Set([...prev, ...mainSteps])]
105
+ })
106
+ }
107
+ }
108
+ }, [location.pathname, totalSteps])
109
+
110
+ const nextStep = useCallback(() => {
111
+ if (activeStep < totalSteps) {
112
+ // Mark current step as completed
113
+ setCompletedSteps(prev => [...prev, activeStep])
114
+ const nextStepNumber = activeStep + 1
115
+ setActiveStep(nextStepNumber)
116
+ onStepComplete?.(activeStep)
117
+
118
+ // Navigate to next step's route
119
+ const nextStep = steps.find(s => s.step === nextStepNumber)
120
+ if (nextStep) {
121
+ navigate(nextStep.path)
122
+ }
123
+ }
124
+ }, [activeStep, totalSteps, onStepComplete, navigate])
125
+
126
+ const previousStep = useCallback(() => {
127
+ if (activeStep > 1) {
128
+ setCompletedSteps(prev => prev.filter(step => step !== activeStep - 1))
129
+ const prevStepNumber = activeStep - 1
130
+ setActiveStep(prevStepNumber)
131
+
132
+ // Navigate to previous step's route
133
+ const prevStep = steps.find(s => s.step === prevStepNumber)
134
+ if (prevStep) {
135
+ navigate(prevStep.path)
136
+ }
137
+ }
138
+ }, [activeStep, navigate])
139
+
140
+ const goToStep = useCallback(
141
+ (step: number) => {
142
+ if (step >= 1 && step <= totalSteps) {
143
+ setActiveStep(step)
144
+ setCompletedSteps(prev => prev.filter(s => s < step))
145
+
146
+ // Navigate to the specified step's route
147
+ const targetStep = steps.find(s => s.step === step)
148
+ if (targetStep) {
149
+ navigate(targetStep.path)
150
+ }
151
+ }
152
+ },
153
+ [totalSteps, navigate]
154
+ )
155
+
156
+ const resetSteps = useCallback(() => {
157
+ setActiveStep(initialStep)
158
+ setCompletedSteps([])
159
+ navigate('/login')
160
+ }, [initialStep, navigate])
161
+
162
+ return {
163
+ activeStep,
164
+ completedSteps,
165
+ nextStep,
166
+ previousStep,
167
+ goToStep,
168
+ resetSteps
169
+ }
170
+ }
package/src/index.css ADDED
@@ -0,0 +1,154 @@
1
+ @import "tailwindcss";
2
+
3
+ @plugin "tailwindcss-animate";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ :root {
8
+ --radius: 0.5rem;
9
+ --background: oklch(98.46% 0.002 247.84);
10
+ --foreground: oklch(0.15 0 0);
11
+ --card: oklch(0.98 0 0);
12
+ --card-foreground: oklch(0.15 0 0);
13
+ --popover: oklch(0.98 0 0);
14
+ --popover-foreground: oklch(0.15 0 0);
15
+ --primary: oklch(59.02% 0.1008 205.29);
16
+ --primary-foreground: oklch(0.99 0 0);
17
+ --secondary: oklch(74.39% 0.1159 203.57);
18
+ --secondary-foreground: oklch(59.02% 0.1008 205.29);
19
+ --muted: oklch(0.95 0 0);
20
+ --muted-foreground: oklch(0.4 0 0);
21
+ --accent: oklch(0.95 0 0);
22
+ --accent-foreground: oklch(59.02% 0.1008 205.29);
23
+ --destructive: oklch(0.5 0.3 30);
24
+ --border: oklch(0.9 0 0);
25
+ --input: oklch(0.9 0 0);
26
+ --ring: oklch(0.7 0 0);
27
+ --chart-1: oklch(0.6 0.3 187.5);
28
+ --chart-2: oklch(0.6 0.3 30);
29
+ --chart-3: oklch(0.6 0.3 240);
30
+ --chart-4: oklch(0.6 0.3 270);
31
+ --chart-5: oklch(0.6 0.3 120);
32
+ --sidebar: oklch(0.98 0 0);
33
+ --sidebar-foreground: oklch(0.15 0 0);
34
+ --sidebar-primary: oklch(59.02% 0.1008 205.29);
35
+ --sidebar-primary-foreground: oklch(0.99 0 0);
36
+ --sidebar-accent: oklch(0.95 0 0);
37
+ --sidebar-accent-foreground: oklch(59.02% 0.1008 205.29);
38
+ --sidebar-border: oklch(0.9 0 0);
39
+ --sidebar-ring: oklch(0.7 0 0);
40
+ --ring: oklch(96.42% 0.0181 205.32);
41
+ --danger: oklch(55.62% 0.2088 24.15);
42
+ --muted-secondary: oklch(54.44% 0.035 265.11);
43
+ --consent-primary: oklch(36.95% 0.0381 260.5);
44
+ --consent-secondary: oklch(0.54 0.035 265.11);
45
+ --placeholder: oklch(0.71 0.0273 261.12);
46
+
47
+
48
+ --border-primary: oklch(87.15% 0.0123 259.82);
49
+ --border-secondary: oklch(0.97 0.0045 258.32);
50
+
51
+ --surface: oklch(0.98 0.0017 247.84);
52
+
53
+ }
54
+
55
+ .dark {
56
+ --background: oklch(20.22% 0.033 232.43);
57
+ /* --background: oklch(0.15 0 0); */
58
+ --foreground: oklch(0.95 0 0);
59
+ --card: oklch(23.81% 0.041 232.18);
60
+ --card-foreground: oklch(0.95 0 0);
61
+ --popover: oklch(0.2 0 0);
62
+ --popover-foreground: oklch(0.95 0 0);
63
+ --primary: oklch(0.6 0.3 187.5);
64
+ --primary-foreground: oklch(0.99 0 0);
65
+ --secondary: oklch(0.25 0 0);
66
+ --secondary-foreground: oklch(0.95 0 0);
67
+ --muted: oklch(0.25 0 0);
68
+ --muted-foreground: oklch(88.91% 0.018 229.03);
69
+ --accent: oklch(0.25 0 0);
70
+ --accent-foreground: oklch(0.95 0 0);
71
+ --destructive: oklch(0.7 0.3 30);
72
+ --border: oklch(30.13% 0.057 236.60);
73
+ --input: oklch(30.13% 0.057 236.60);
74
+ --ring: oklch(0.5 0 0);
75
+ --chart-1: oklch(0.4 0.3 187.5);
76
+ --chart-2: oklch(0.4 0.3 30);
77
+ --chart-3: oklch(0.4 0.3 240);
78
+ --chart-4: oklch(0.4 0.3 270);
79
+ --chart-5: oklch(0.4 0.3 120);
80
+ --sidebar: oklch(0.2 0 0);
81
+ --sidebar-foreground: oklch(0.95 0 0);
82
+ --sidebar-primary: oklch(0.4 0.3 187.5);
83
+ --sidebar-primary-foreground: oklch(0.99 0 0);
84
+ --sidebar-accent: oklch(0.25 0 0);
85
+ --sidebar-accent-foreground: oklch(0.95 0 0);
86
+ --sidebar-border: oklch(0.3 0 0);
87
+ --sidebar-ring: oklch(0.5 0 0);
88
+ --muted-secondary: oklch(77.53% 0.036 227.57);
89
+ --ring: oklch(30.13% 0.057 236.60);
90
+ }
91
+
92
+ @theme inline {
93
+ --radius-sm: calc(var(--radius) - 4px);
94
+ --radius-md: calc(var(--radius) - 2px);
95
+ --radius-lg: var(--radius);
96
+ --radius-xl: calc(var(--radius) + 4px);
97
+ --color-background: var(--background);
98
+ --color-foreground: var(--foreground);
99
+ --color-card: var(--card);
100
+ --color-card-foreground: var(--card-foreground);
101
+ --color-popover: var(--popover);
102
+ --color-popover-foreground: var(--popover-foreground);
103
+ --color-primary: var(--primary);
104
+ --color-primary-foreground: var(--primary-foreground);
105
+ --color-secondary: var(--secondary);
106
+ --color-secondary-foreground: var(--secondary-foreground);
107
+ --color-muted: var(--muted);
108
+ --color-muted-foreground: var(--muted-foreground);
109
+ --color-accent: var(--accent);
110
+ --color-accent-foreground: var(--accent-foreground);
111
+ --color-destructive: var(--destructive);
112
+ --color-border: var(--border);
113
+ --color-input: var(--input);
114
+ --color-ring: var(--ring);
115
+ --color-chart-1: var(--chart-1);
116
+ --color-chart-2: var(--chart-2);
117
+ --color-chart-3: var(--chart-3);
118
+ --color-chart-4: var(--chart-4);
119
+ --color-chart-5: var(--chart-5);
120
+ --color-sidebar: var(--sidebar);
121
+ --color-sidebar-foreground: var(--sidebar-foreground);
122
+ --color-sidebar-primary: var(--sidebar-primary);
123
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
124
+ --color-sidebar-accent: var(--sidebar-accent);
125
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
126
+ --color-sidebar-border: var(--sidebar-border);
127
+ --color-sidebar-ring: var(--sidebar-ring);
128
+ --color-danger: var(--danger);
129
+ --color-muted-secondary: var(--muted-secondary);
130
+ --color-consent-primary: var(--consent-primary);
131
+ --color-consent-secondary: var(--consent-secondary);
132
+ --color-placeholder: var(--placeholder);
133
+
134
+ --color-border-primary: var(--border-primary);
135
+ --color-border-secondary: var(--border-secondary);
136
+
137
+ --color-surface: var(--surface);
138
+
139
+ }
140
+
141
+ @layer base {
142
+ * {
143
+ @apply border-border outline-ring/50;
144
+ }
145
+ body {
146
+ @apply bg-background text-foreground;
147
+ color: #344054;
148
+ }
149
+ }
150
+
151
+ body {
152
+ font-family: 'Poppins', sans-serif;
153
+
154
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Type definitions for App component and related functionality
3
+ */
4
+
5
+ // Props for URL decode handler component
6
+ export interface UrlDecodeHandlerProps {
7
+ children: React.ReactNode;
8
+ }
9
+
10
+ // Custom style interface used in redirect store
11
+ export interface CustomStyle {
12
+ primaryButtonColor?: string;
13
+ primaryBackgroundColor?: string;
14
+ secondaryButtonColor?: string;
15
+ textColor?: string;
16
+ backgroundColor?: string;
17
+ logoUrl?: string;
18
+ }
19
+
20
+ // Decoded information from URL
21
+ export interface DecodedInfo {
22
+ redirectUrl?: string;
23
+ entityId?: string;
24
+ purpose?: string;
25
+ customStyle?: CustomStyle;
26
+ [key: string]: any;
27
+ }
28
+
29
+ // Theme context related interfaces
30
+ export interface ThemeProviderProps {
31
+ children: React.ReactNode;
32
+ defaultTheme: string;
33
+ }
34
+
35
+ // Navigation block context types
36
+ export interface NavigationBlockContextType {
37
+ allowNavigation: () => void;
38
+ shouldAllowNavigation: () => boolean;
39
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Type definitions for API services
3
+ */
4
+ import { InternalAxiosRequestConfig } from 'axios';
5
+
6
+ // Common API response interfaces
7
+ export interface ApiResponse<T = any> {
8
+ data: T;
9
+ success: boolean;
10
+ message?: string;
11
+ }
12
+
13
+ // Auth related interfaces
14
+ export interface LoginRequest {
15
+ phone: string;
16
+ otp: string;
17
+ }
18
+
19
+ export interface TokenResponse {
20
+ access_token: string;
21
+ refresh_token: string;
22
+ token_type: string;
23
+ expires_in: number;
24
+ }
25
+
26
+ export interface UserProfile {
27
+ id: string;
28
+ name: string;
29
+ phone: string;
30
+ email?: string;
31
+ }
32
+
33
+ // Axios request interface with retry flag
34
+ export interface RetryableRequest extends InternalAxiosRequestConfig {
35
+ _retry?: boolean;
36
+ }
37
+
38
+ // Account related interfaces
39
+ export interface AccountDetails {
40
+ id: string;
41
+ accountNumber: string;
42
+ accountType: string;
43
+ bankName: string;
44
+ ifsc?: string;
45
+ linkedAt?: string;
46
+ }
47
+
48
+ // FIP related interfaces
49
+ export interface FipDetails {
50
+ id: string;
51
+ name: string;
52
+ logo: string;
53
+ type: string;
54
+ category?: string;
55
+ }
56
+
57
+ // Consent related interfaces
58
+ export interface ConsentDetails {
59
+ id: string;
60
+ purpose: string;
61
+ dataRequested: string[];
62
+ expiry: string;
63
+ approvedAt?: string;
64
+ status: 'pending' | 'approved' | 'rejected';
65
+ }