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,432 @@
|
|
1
|
+
# SAAFE Redirection Flow - Technical Architecture
|
2
|
+
|
3
|
+
This document outlines the technical architecture of the SAAFE Redirection Flow application, including its structure, state management, and key technical decisions.
|
4
|
+
|
5
|
+
## Application Architecture
|
6
|
+
|
7
|
+
The SAAFE Redirection Flow follows a modern React application architecture with the following key characteristics:
|
8
|
+
|
9
|
+
- **Component-Based Architecture**: UI elements are broken down into reusable components
|
10
|
+
- **Custom Hooks**: Business logic is encapsulated in custom hooks
|
11
|
+
- **Context API**: Used for theme management and global state that doesn't require complex updates
|
12
|
+
- **Store Management**: Zustand for state that requires more complex updates
|
13
|
+
- **Routing**: React Router for navigation between screens
|
14
|
+
|
15
|
+
```
|
16
|
+
SAAFE Redirection Flow
|
17
|
+
│
|
18
|
+
├── Core
|
19
|
+
│ ├── Routing (React Router)
|
20
|
+
│ ├── API Integration (Axios + React Query)
|
21
|
+
│ └── State Management (Zustand + Context API)
|
22
|
+
│
|
23
|
+
├── Features
|
24
|
+
│ ├── Authentication
|
25
|
+
│ ├── Account Discovery
|
26
|
+
│ ├── Consent Management
|
27
|
+
│ └── Platform-Specific Behavior
|
28
|
+
│
|
29
|
+
└── UI Layer
|
30
|
+
├── Components
|
31
|
+
├── Pages
|
32
|
+
└── Theming
|
33
|
+
```
|
34
|
+
|
35
|
+
## Folder Structure
|
36
|
+
|
37
|
+
The application follows a feature-based folder structure:
|
38
|
+
|
39
|
+
```
|
40
|
+
src/
|
41
|
+
├── assets/ # Static assets like images and icons
|
42
|
+
├── components/ # Reusable UI components
|
43
|
+
│ ├── auth/ # Authentication-related components
|
44
|
+
│ ├── ui/ # Base UI components (buttons, inputs, etc.)
|
45
|
+
│ └── ...
|
46
|
+
├── config/ # Configuration files and constants
|
47
|
+
├── contexts/ # React context providers
|
48
|
+
├── hooks/ # Custom React hooks
|
49
|
+
├── interfaces/ # TypeScript interfaces and type definitions
|
50
|
+
├── lib/ # Core utilities and helper functions
|
51
|
+
├── locales/ # Internationalization resources
|
52
|
+
├── pages/ # Page components
|
53
|
+
│ ├── accounts/ # Account-related pages
|
54
|
+
│ ├── consent/ # Consent flow pages
|
55
|
+
│ └── ...
|
56
|
+
├── providers/ # Service providers (Query, Toast, etc.)
|
57
|
+
├── services/ # API services
|
58
|
+
├── store/ # Zustand stores
|
59
|
+
├── styles/ # Global styles and theme definitions
|
60
|
+
├── utils/ # Utility functions
|
61
|
+
├── App.tsx # Main app component with routing
|
62
|
+
└── main.tsx # Entry point
|
63
|
+
```
|
64
|
+
|
65
|
+
## State Management
|
66
|
+
|
67
|
+
The application uses a hybrid state management approach:
|
68
|
+
|
69
|
+
### Local Component State
|
70
|
+
|
71
|
+
Used for component-specific state that doesn't need to be shared.
|
72
|
+
|
73
|
+
```tsx
|
74
|
+
function Counter() {
|
75
|
+
const [count, setCount] = useState(0);
|
76
|
+
|
77
|
+
return (
|
78
|
+
<button onClick={() => setCount(count + 1)}>
|
79
|
+
Count: {count}
|
80
|
+
</button>
|
81
|
+
);
|
82
|
+
}
|
83
|
+
```
|
84
|
+
|
85
|
+
### Context API
|
86
|
+
|
87
|
+
Used for theme management and other global states that don't require complex updates.
|
88
|
+
|
89
|
+
```tsx
|
90
|
+
// src/contexts/ThemeContext.tsx
|
91
|
+
export const ThemeProvider: React.FC<ThemeProviderProps> = ({
|
92
|
+
children,
|
93
|
+
defaultTheme = "system"
|
94
|
+
}) => {
|
95
|
+
// Implementation details
|
96
|
+
|
97
|
+
return (
|
98
|
+
<ThemeContext.Provider value={{ theme, setTheme }}>
|
99
|
+
{children}
|
100
|
+
</ThemeContext.Provider>
|
101
|
+
);
|
102
|
+
};
|
103
|
+
```
|
104
|
+
|
105
|
+
### Zustand Stores
|
106
|
+
|
107
|
+
Used for more complex state management needs, particularly for data that needs to be accessed across multiple components.
|
108
|
+
|
109
|
+
```tsx
|
110
|
+
// src/store/redirect.store.ts
|
111
|
+
export const useRedirectStore = create<RedirectStore>((set, get) => ({
|
112
|
+
decodedInfo: null,
|
113
|
+
setDecodedInfo: (info) => set({ decodedInfo: info }),
|
114
|
+
getCustomStyle: () => {
|
115
|
+
const { decodedInfo } = get();
|
116
|
+
if (!decodedInfo?.styleOptions) return null;
|
117
|
+
return decodedInfo.styleOptions;
|
118
|
+
}
|
119
|
+
}));
|
120
|
+
```
|
121
|
+
|
122
|
+
## Data Fetching
|
123
|
+
|
124
|
+
The application uses React Query for data fetching and caching:
|
125
|
+
|
126
|
+
```tsx
|
127
|
+
// Example of a query hook
|
128
|
+
export function useAccounts() {
|
129
|
+
const { decodedInfo } = useRedirectStore();
|
130
|
+
|
131
|
+
return useQuery({
|
132
|
+
queryKey: ['accounts', decodedInfo?.userId],
|
133
|
+
queryFn: () => fetchAccounts(decodedInfo?.userId),
|
134
|
+
enabled: !!decodedInfo?.userId,
|
135
|
+
});
|
136
|
+
}
|
137
|
+
```
|
138
|
+
|
139
|
+
## Routing and Navigation
|
140
|
+
|
141
|
+
React Router v7 is used for routing, with route protection provided by an `AuthGuard` component:
|
142
|
+
|
143
|
+
```tsx
|
144
|
+
// Protected route example from App.tsx
|
145
|
+
<Route
|
146
|
+
path="link-accounts/discovery"
|
147
|
+
element={
|
148
|
+
<AuthGuard>
|
149
|
+
<Discover />
|
150
|
+
</AuthGuard>
|
151
|
+
}
|
152
|
+
/>
|
153
|
+
```
|
154
|
+
|
155
|
+
## Theming
|
156
|
+
|
157
|
+
The application supports multiple themes (light, dark, system) using CSS variables and the Context API:
|
158
|
+
|
159
|
+
```tsx
|
160
|
+
// Theme switching logic
|
161
|
+
const switchTheme = (newTheme: Theme) => {
|
162
|
+
// Update theme in context
|
163
|
+
setTheme(newTheme);
|
164
|
+
|
165
|
+
// Apply theme to document
|
166
|
+
const root = document.documentElement;
|
167
|
+
if (newTheme === 'dark' || (newTheme === 'system' && prefersDarkMode)) {
|
168
|
+
root.classList.add('dark');
|
169
|
+
} else {
|
170
|
+
root.classList.remove('dark');
|
171
|
+
}
|
172
|
+
};
|
173
|
+
```
|
174
|
+
|
175
|
+
## Internationalization
|
176
|
+
|
177
|
+
The application uses i18next for internationalization:
|
178
|
+
|
179
|
+
```tsx
|
180
|
+
// i18n setup
|
181
|
+
import i18n from 'i18next';
|
182
|
+
import { initReactI18next } from 'react-i18next';
|
183
|
+
import LanguageDetector from 'i18next-browser-languagedetector';
|
184
|
+
|
185
|
+
i18n
|
186
|
+
.use(LanguageDetector)
|
187
|
+
.use(initReactI18next)
|
188
|
+
.init({
|
189
|
+
resources: {
|
190
|
+
en: { translation: enTranslation },
|
191
|
+
hi: { translation: hiTranslation },
|
192
|
+
// other languages...
|
193
|
+
},
|
194
|
+
fallbackLng: 'en',
|
195
|
+
interpolation: {
|
196
|
+
escapeValue: false,
|
197
|
+
},
|
198
|
+
});
|
199
|
+
```
|
200
|
+
|
201
|
+
## Form Handling
|
202
|
+
|
203
|
+
The application uses React Hook Form with Zod validation:
|
204
|
+
|
205
|
+
```tsx
|
206
|
+
// Form with validation example
|
207
|
+
const formSchema = z.object({
|
208
|
+
username: z.string().min(3, "Username must be at least 3 characters"),
|
209
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
210
|
+
});
|
211
|
+
|
212
|
+
function LoginForm() {
|
213
|
+
const form = useForm<z.infer<typeof formSchema>>({
|
214
|
+
resolver: zodResolver(formSchema),
|
215
|
+
defaultValues: {
|
216
|
+
username: "",
|
217
|
+
password: "",
|
218
|
+
},
|
219
|
+
});
|
220
|
+
|
221
|
+
// Form handling logic...
|
222
|
+
}
|
223
|
+
```
|
224
|
+
|
225
|
+
## Analytics
|
226
|
+
|
227
|
+
The application integrates with PostHog for analytics:
|
228
|
+
|
229
|
+
```tsx
|
230
|
+
// Analytics tracking example
|
231
|
+
import { trackEvent, EVENTS } from "@/utils/posthog";
|
232
|
+
|
233
|
+
// In component
|
234
|
+
useEffect(() => {
|
235
|
+
trackEvent(EVENTS.PAGE_VIEW, {
|
236
|
+
page: "login",
|
237
|
+
timestamp: new Date().toISOString()
|
238
|
+
});
|
239
|
+
}, []);
|
240
|
+
```
|
241
|
+
|
242
|
+
## Security Features
|
243
|
+
|
244
|
+
### URL Parameter Decoding
|
245
|
+
|
246
|
+
The application securely decodes URL parameters to extract information:
|
247
|
+
|
248
|
+
```tsx
|
249
|
+
// URL decoding hook
|
250
|
+
export function useUrlDecode() {
|
251
|
+
const [isLoading, setIsLoading] = useState(true);
|
252
|
+
const [isError, setIsError] = useState(false);
|
253
|
+
const [error, setError] = useState<Error | null>(null);
|
254
|
+
const { setDecodedInfo } = useRedirectStore();
|
255
|
+
|
256
|
+
useEffect(() => {
|
257
|
+
const decodeUrlParams = async () => {
|
258
|
+
try {
|
259
|
+
// Get URL parameters
|
260
|
+
const params = new URLSearchParams(window.location.search);
|
261
|
+
const fi = params.get('fi');
|
262
|
+
const ecreq = params.get('ecreq');
|
263
|
+
const reqdate = params.get('reqdate');
|
264
|
+
|
265
|
+
// Validate required parameters
|
266
|
+
if (!fi || !ecreq || !reqdate) {
|
267
|
+
throw new Error('Missing required URL parameters');
|
268
|
+
}
|
269
|
+
|
270
|
+
// Decode encrypted request
|
271
|
+
const decodedInfo = await decodeRequest(fi, ecreq, reqdate);
|
272
|
+
setDecodedInfo(decodedInfo);
|
273
|
+
|
274
|
+
} catch (err) {
|
275
|
+
setIsError(true);
|
276
|
+
setError(err instanceof Error ? err : new Error('Unknown error'));
|
277
|
+
} finally {
|
278
|
+
setIsLoading(false);
|
279
|
+
}
|
280
|
+
};
|
281
|
+
|
282
|
+
decodeUrlParams();
|
283
|
+
}, [setDecodedInfo]);
|
284
|
+
|
285
|
+
return { isLoading, isError, error };
|
286
|
+
}
|
287
|
+
```
|
288
|
+
|
289
|
+
### Navigation Protection
|
290
|
+
|
291
|
+
The application prevents accidental navigation away from the flow:
|
292
|
+
|
293
|
+
```tsx
|
294
|
+
// Navigation block logic
|
295
|
+
useEffect(() => {
|
296
|
+
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
297
|
+
e.preventDefault();
|
298
|
+
e.returnValue = '';
|
299
|
+
};
|
300
|
+
|
301
|
+
window.addEventListener('beforeunload', handleBeforeUnload);
|
302
|
+
return () => {
|
303
|
+
window.removeEventListener('beforeunload', handleBeforeUnload);
|
304
|
+
};
|
305
|
+
}, []);
|
306
|
+
```
|
307
|
+
|
308
|
+
## Platform-Specific Behavior
|
309
|
+
|
310
|
+
The application adapts its behavior based on the platform parameter:
|
311
|
+
|
312
|
+
```tsx
|
313
|
+
// Platform detection hook
|
314
|
+
export function usePlatform() {
|
315
|
+
const { decodedInfo } = useRedirectStore();
|
316
|
+
const platform = decodedInfo?.platform || 'web';
|
317
|
+
|
318
|
+
const isNativeSDK = ['ios', 'android', 'react-native', 'flutter'].includes(platform);
|
319
|
+
|
320
|
+
return {
|
321
|
+
platform,
|
322
|
+
isNativeSDK,
|
323
|
+
isIOS: platform === 'ios',
|
324
|
+
isAndroid: platform === 'android',
|
325
|
+
isReactNative: platform === 'react-native',
|
326
|
+
isFlutter: platform === 'flutter',
|
327
|
+
isWeb: platform === 'web',
|
328
|
+
};
|
329
|
+
}
|
330
|
+
```
|
331
|
+
|
332
|
+
## Performance Optimizations
|
333
|
+
|
334
|
+
### Code Splitting
|
335
|
+
|
336
|
+
React's lazy loading is used for code splitting:
|
337
|
+
|
338
|
+
```tsx
|
339
|
+
// Lazy loaded components
|
340
|
+
const ReviewConsent = React.lazy(() => import('./pages/consent/ReviewConsent'));
|
341
|
+
const Success = React.lazy(() => import('./pages/consent/success'));
|
342
|
+
```
|
343
|
+
|
344
|
+
### Memoization
|
345
|
+
|
346
|
+
React.memo and useMemo/useCallback are used to prevent unnecessary re-renders:
|
347
|
+
|
348
|
+
```tsx
|
349
|
+
// Memoized component example
|
350
|
+
const MemoizedComponent = React.memo(({ value }) => {
|
351
|
+
return <div>{value}</div>;
|
352
|
+
});
|
353
|
+
|
354
|
+
// In parent component
|
355
|
+
const handleClick = useCallback(() => {
|
356
|
+
// Handle click
|
357
|
+
}, []);
|
358
|
+
|
359
|
+
const computedValue = useMemo(() => {
|
360
|
+
return expensiveComputation(value);
|
361
|
+
}, [value]);
|
362
|
+
```
|
363
|
+
|
364
|
+
## Testing Strategy
|
365
|
+
|
366
|
+
The application is set up for comprehensive testing:
|
367
|
+
|
368
|
+
- **Unit Tests**: For individual components and hooks
|
369
|
+
- **Integration Tests**: For feature workflows
|
370
|
+
- **Visual Tests**: Using Storybook
|
371
|
+
- **E2E Tests**: Using Playwright
|
372
|
+
|
373
|
+
```tsx
|
374
|
+
// Example test
|
375
|
+
import { render, screen } from '@testing-library/react';
|
376
|
+
import userEvent from '@testing-library/user-event';
|
377
|
+
import Button from './Button';
|
378
|
+
|
379
|
+
test('button calls onClick when clicked', async () => {
|
380
|
+
const handleClick = vi.fn();
|
381
|
+
render(<Button onClick={handleClick}>Click me</Button>);
|
382
|
+
|
383
|
+
await userEvent.click(screen.getByText('Click me'));
|
384
|
+
|
385
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
386
|
+
});
|
387
|
+
```
|
388
|
+
|
389
|
+
## Build and Deployment
|
390
|
+
|
391
|
+
The application uses Vite for fast development and optimized production builds:
|
392
|
+
|
393
|
+
```js
|
394
|
+
// vite.config.ts
|
395
|
+
import { defineConfig } from 'vite';
|
396
|
+
import react from '@vitejs/plugin-react';
|
397
|
+
|
398
|
+
export default defineConfig({
|
399
|
+
plugins: [react()],
|
400
|
+
build: {
|
401
|
+
minify: 'terser',
|
402
|
+
sourcemap: true,
|
403
|
+
},
|
404
|
+
resolve: {
|
405
|
+
alias: {
|
406
|
+
'@': '/src',
|
407
|
+
},
|
408
|
+
},
|
409
|
+
});
|
410
|
+
```
|
411
|
+
|
412
|
+
## Technical Decisions and Tradeoffs
|
413
|
+
|
414
|
+
### Why React Router v7?
|
415
|
+
|
416
|
+
React Router v7 was chosen for its declarative routing and improved performance over previous versions. The application benefits from nested routes and route-level code splitting.
|
417
|
+
|
418
|
+
### Why Zustand over Redux?
|
419
|
+
|
420
|
+
Zustand was selected for its simplicity and minimal boilerplate compared to Redux, while still providing powerful state management capabilities. It integrates well with React's hooks and has a smaller bundle size.
|
421
|
+
|
422
|
+
### Why React Query?
|
423
|
+
|
424
|
+
React Query simplifies data fetching with built-in caching, loading and error states, and automatic refetching. It helps keep components clean by separating data fetching concerns.
|
425
|
+
|
426
|
+
### Why CSS-in-JS Approach?
|
427
|
+
|
428
|
+
The application uses a CSS-in-JS approach for styling to:
|
429
|
+
- Keep styles close to components
|
430
|
+
- Enable theme variables to be accessed in JavaScript
|
431
|
+
- Allow dynamic styling based on props
|
432
|
+
- Prevent style conflicts through scoped CSS
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# SAAFE Components Documentation
|
2
|
+
|
3
|
+
This document provides an overview of the UI components used throughout the SAAFE Redirection Flow application.
|
4
|
+
|
5
|
+
## Core Components
|
6
|
+
|
7
|
+
### Layout Components
|
8
|
+
|
9
|
+
- **FrostedPanel**: A glass-like container with backdrop blur effect for modern UI elements
|
10
|
+
- **Card**: Basic container with shadow and rounded corners for content grouping
|
11
|
+
- **Dialog**: Modal dialog component for displaying information that requires user attention
|
12
|
+
- **BottomSheet**: Mobile-first slide-up panel for displaying contextual content
|
13
|
+
- **Sheet**: Slide-in panel from the side of the screen
|
14
|
+
- **Modal**: Centered modal dialog with backdrop
|
15
|
+
- **Sidebar**: Navigation sidebar with collapsible sections
|
16
|
+
|
17
|
+
### Input Components
|
18
|
+
|
19
|
+
- **Button**: Primary action component with multiple variants (primary, secondary, ghost, etc.)
|
20
|
+
- **AnimatedButton**: Button with loading animation states
|
21
|
+
- **Input**: Text input field with validation support
|
22
|
+
- **Checkbox**: Selectable checkbox input component
|
23
|
+
- **RadioGroup**: Group of radio button inputs for mutually exclusive selection
|
24
|
+
- **Select**: Dropdown select component for choosing from a list of options
|
25
|
+
- **OTPInput**: One-time password input with individual character fields
|
26
|
+
- **Calendar**: Date picker component with day/month/year selection
|
27
|
+
|
28
|
+
### Navigation Components
|
29
|
+
|
30
|
+
- **Step**: Step component for multi-step flows with progress indicators
|
31
|
+
- **Stepper**: Container for multi-step processes with navigation controls
|
32
|
+
- **DotStepper**: Minimal stepper with dots for indicating progress
|
33
|
+
- **StepperProgress**: Progress bar for multi-step flows
|
34
|
+
- **Tabs**: Tabbed navigation interface for switching between content sections
|
35
|
+
|
36
|
+
### Feedback Components
|
37
|
+
|
38
|
+
- **Alert**: Alert component for displaying information, warnings, or errors
|
39
|
+
- **Skeleton**: Loading placeholder for content that is being loaded
|
40
|
+
- **Tooltip**: Informational tooltip that appears on hover
|
41
|
+
- **Progress**: Progress indicator for operations
|
42
|
+
|
43
|
+
### Document Components
|
44
|
+
|
45
|
+
- **DocumentLink**: Component for linking to external documents with platform-specific handling
|
46
|
+
- **ScrollArea**: Scrollable container with custom scrollbar styling
|
47
|
+
|
48
|
+
### Utility Components
|
49
|
+
|
50
|
+
- **Separator**: Visual divider between content sections
|
51
|
+
- **Avatar**: User avatar component with fallback support
|
52
|
+
- **Label**: Form label component with accessibility features
|
53
|
+
- **Popover**: Floating content that appears relative to a trigger element
|
54
|
+
- **Collapsible**: Expandable/collapsible content section
|
55
|
+
- **DropdownMenu**: Menu that appears relative to a trigger element
|
56
|
+
|
57
|
+
## Platform-Specific Components
|
58
|
+
|
59
|
+
- **MobileFooter**: Footer component optimized for mobile devices
|
60
|
+
- **WebFooter**: Footer component optimized for web browsers
|
61
|
+
- **PlatformSpecificBehavior**: Component that renders different content based on the platform
|
62
|
+
- **MobileBackground**: Background component with mobile-specific styling
|
63
|
+
- **UrlDecodeLoader**: Loading component displayed during URL parameter decoding
|
64
|
+
|
65
|
+
## Form Components
|
66
|
+
|
67
|
+
- **Form**: Form container with validation and submission handling
|
68
|
+
- **FormField**: Field wrapper with label and error handling
|
69
|
+
- **FormItem**: Individual form item container
|
70
|
+
- **FormLabel**: Accessible form label
|
71
|
+
- **FormControl**: Form control wrapper with accessibility attributes
|
72
|
+
- **FormDescription**: Descriptive text for form fields
|
73
|
+
- **FormMessage**: Error or validation message for form fields
|
74
|
+
|
75
|
+
## Authentication Components
|
76
|
+
|
77
|
+
- **AuthGuard**: Component that restricts access to protected routes
|
78
|
+
|
79
|
+
## Specialized Components
|
80
|
+
|
81
|
+
- **ModeToggle**: Theme toggle switch between light and dark modes
|
82
|
+
- **PageHeader**: Header component with title and optional back button
|
83
|
+
- **StepNavigation**: Navigation component for multi-step flows
|
84
|
+
- **SDKParamsDocs**: Documentation component for SDK parameters
|
85
|
+
- **DummyFooter**: Simple footer component for demo purposes
|
86
|
+
- **MobileAppDownload**: Component for promoting mobile app downloads
|
87
|
+
|
88
|
+
## Usage Examples
|
89
|
+
|
90
|
+
### Basic Button
|
91
|
+
|
92
|
+
```tsx
|
93
|
+
import { Button } from "@/components/ui/button";
|
94
|
+
|
95
|
+
export function MyComponent() {
|
96
|
+
return (
|
97
|
+
<Button variant="primary" size="default" onClick={() => console.log("Clicked")}>
|
98
|
+
Click Me
|
99
|
+
</Button>
|
100
|
+
);
|
101
|
+
}
|
102
|
+
```
|
103
|
+
|
104
|
+
### Form with Validation
|
105
|
+
|
106
|
+
```tsx
|
107
|
+
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
|
108
|
+
import { Input } from "@/components/ui/input";
|
109
|
+
import { useForm } from "react-hook-form";
|
110
|
+
import { z } from "zod";
|
111
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
112
|
+
|
113
|
+
const formSchema = z.object({
|
114
|
+
email: z.string().email("Invalid email address"),
|
115
|
+
});
|
116
|
+
|
117
|
+
export function LoginForm() {
|
118
|
+
const form = useForm({
|
119
|
+
resolver: zodResolver(formSchema),
|
120
|
+
defaultValues: {
|
121
|
+
email: "",
|
122
|
+
},
|
123
|
+
});
|
124
|
+
|
125
|
+
const onSubmit = (data) => {
|
126
|
+
console.log(data);
|
127
|
+
};
|
128
|
+
|
129
|
+
return (
|
130
|
+
<Form {...form}>
|
131
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
132
|
+
<FormField
|
133
|
+
control={form.control}
|
134
|
+
name="email"
|
135
|
+
render={({ field }) => (
|
136
|
+
<FormItem>
|
137
|
+
<FormLabel>Email</FormLabel>
|
138
|
+
<FormControl>
|
139
|
+
<Input placeholder="Enter your email" {...field} />
|
140
|
+
</FormControl>
|
141
|
+
<FormMessage />
|
142
|
+
</FormItem>
|
143
|
+
)}
|
144
|
+
/>
|
145
|
+
<Button type="submit">Submit</Button>
|
146
|
+
</form>
|
147
|
+
</Form>
|
148
|
+
);
|
149
|
+
}
|
150
|
+
```
|
151
|
+
|
152
|
+
### Multi-step Flow
|
153
|
+
|
154
|
+
```tsx
|
155
|
+
import { Stepper, Step } from "@/components/ui/stepper";
|
156
|
+
|
157
|
+
export function OnboardingFlow() {
|
158
|
+
const [currentStep, setCurrentStep] = useState(0);
|
159
|
+
|
160
|
+
return (
|
161
|
+
<Stepper currentStep={currentStep} totalSteps={3}>
|
162
|
+
<Step
|
163
|
+
title="Personal Details"
|
164
|
+
description="Enter your personal information"
|
165
|
+
isActive={currentStep === 0}
|
166
|
+
>
|
167
|
+
{/* Step 1 content */}
|
168
|
+
</Step>
|
169
|
+
<Step
|
170
|
+
title="Account Setup"
|
171
|
+
description="Set up your account"
|
172
|
+
isActive={currentStep === 1}
|
173
|
+
>
|
174
|
+
{/* Step 2 content */}
|
175
|
+
</Step>
|
176
|
+
<Step
|
177
|
+
title="Confirmation"
|
178
|
+
description="Confirm your details"
|
179
|
+
isActive={currentStep === 2}
|
180
|
+
>
|
181
|
+
{/* Step 3 content */}
|
182
|
+
</Step>
|
183
|
+
</Stepper>
|
184
|
+
);
|
185
|
+
}
|
186
|
+
```
|
187
|
+
|
188
|
+
## Component Customization
|
189
|
+
|
190
|
+
Most components support customization through props. Common customization options include:
|
191
|
+
|
192
|
+
- **variant**: Changes the visual style (primary, secondary, ghost, etc.)
|
193
|
+
- **size**: Changes the size (sm, md, lg, etc.)
|
194
|
+
- **disabled**: Disables the component
|
195
|
+
- **className**: Allows for additional CSS classes for custom styling
|
196
|
+
|
197
|
+
## Theming
|
198
|
+
|
199
|
+
Components automatically adapt to the current theme (light/dark) based on the ThemeProvider configuration. Custom themes can be applied by modifying CSS variables in the root document element.
|
package/docs/index.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# SAAFE Redirection Flow Documentation
|
2
|
+
|
3
|
+
Welcome to the SAAFE Redirection Flow documentation. This documentation provides comprehensive information about the application's features, components, and integration options.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
- [Architecture](./architecture.md) - Technical architecture and design decisions
|
8
|
+
- [Components](./components.md) - UI components library documentation
|
9
|
+
- [Routes](./routes.md) - Available routes and pages
|
10
|
+
- [SDK Integration](./sdk-integration.md) - Guide for integrating the SAAFE Redirection Flow into your applications
|
11
|
+
- [User Journey](./user-flow.md) - Detailed explanation of the end-to-end user flow
|
12
|
+
|
13
|
+
## Project Overview
|
14
|
+
|
15
|
+
The SAAFE Redirection Flow is a modern React application that provides a secure account linking experience for the SAAFE (Secure Account Access Financial Environment) platform. It is designed to be embedded in mobile and web applications via an SDK.
|
16
|
+
|
17
|
+
The application facilitates secure account linking between financial institutions and third-party applications through a user-friendly interface, with features including:
|
18
|
+
|
19
|
+
- Multi-platform support (iOS, Android, React Native, Flutter, Web)
|
20
|
+
- Theming options (light, dark, system)
|
21
|
+
- Internationalization
|
22
|
+
- Responsive design
|
23
|
+
- Secure authentication
|
24
|
+
- Account discovery and linking
|
25
|
+
- Consent management
|
26
|
+
|
27
|
+
## Getting Started
|
28
|
+
|
29
|
+
Before diving into specific sections, we recommend understanding the basic flow of the application:
|
30
|
+
|
31
|
+
1. **Integration**: Embed the application in your platform using the appropriate integration method
|
32
|
+
2. **Configuration**: Configure the application using URL parameters
|
33
|
+
3. **Authentication**: Users authenticate with their financial institution
|
34
|
+
4. **Account Discovery**: Users select accounts to link
|
35
|
+
5. **Consent**: Users review and approve data sharing
|
36
|
+
6. **Completion**: The flow completes with success or rejection
|
37
|
+
|
38
|
+
## Core Features
|
39
|
+
|
40
|
+
### Multi-platform Support
|
41
|
+
|
42
|
+
The application is designed to work seamlessly across different platforms:
|
43
|
+
|
44
|
+
- **iOS/Android**: Optimized for mobile WebViews with adjusted UI elements
|
45
|
+
- **React Native/Flutter**: Suitable configurations for cross-platform SDKs
|
46
|
+
- **Web**: Default desktop experience with navigation safeguards
|
47
|
+
|
48
|
+
### Theming
|
49
|
+
|
50
|
+
The application supports multiple themes:
|
51
|
+
|
52
|
+
- **Light**: Bright interface with dark text
|
53
|
+
- **Dark**: Dark interface with light text
|
54
|
+
- **System**: Adapts to the user's system preferences
|
55
|
+
|
56
|
+
### Document Handling
|
57
|
+
|
58
|
+
The application handles document displays differently based on the platform:
|
59
|
+
|
60
|
+
- **Web**: Terms and conditions, privacy policy and other documents open in new tabs
|
61
|
+
- **Mobile/SDK platforms**: Documents are displayed in a modern, centered modal dialog with local React components
|
62
|
+
|
63
|
+
### Navigation Protection
|
64
|
+
|
65
|
+
To prevent accidental exits, the application implements navigation protection that warns users before leaving the flow.
|
66
|
+
|
67
|
+
## Contributing
|
68
|
+
|
69
|
+
For information on contributing to the SAAFE Redirection Flow, please contact the development team.
|