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,165 @@
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { Slot } from "@radix-ui/react-slot"
4
+ import {
5
+ Controller,
6
+ FormProvider,
7
+ useFormContext,
8
+ useFormState,
9
+ type ControllerProps,
10
+ type FieldPath,
11
+ type FieldValues,
12
+ } from "react-hook-form"
13
+
14
+ import { cn } from "@/lib/utils"
15
+ import { Label } from "@/components/ui/label"
16
+
17
+ const Form = FormProvider
18
+
19
+ type FormFieldContextValue<
20
+ TFieldValues extends FieldValues = FieldValues,
21
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
22
+ > = {
23
+ name: TName
24
+ }
25
+
26
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
27
+ {} as FormFieldContextValue
28
+ )
29
+
30
+ const FormField = <
31
+ TFieldValues extends FieldValues = FieldValues,
32
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
33
+ >({
34
+ ...props
35
+ }: ControllerProps<TFieldValues, TName>) => {
36
+ return (
37
+ <FormFieldContext.Provider value={{ name: props.name }}>
38
+ <Controller {...props} />
39
+ </FormFieldContext.Provider>
40
+ )
41
+ }
42
+
43
+ const useFormField = () => {
44
+ const fieldContext = React.useContext(FormFieldContext)
45
+ const itemContext = React.useContext(FormItemContext)
46
+ const { getFieldState } = useFormContext()
47
+ const formState = useFormState({ name: fieldContext.name })
48
+ const fieldState = getFieldState(fieldContext.name, formState)
49
+
50
+ if (!fieldContext) {
51
+ throw new Error("useFormField should be used within <FormField>")
52
+ }
53
+
54
+ const { id } = itemContext
55
+
56
+ return {
57
+ id,
58
+ name: fieldContext.name,
59
+ formItemId: `${id}-form-item`,
60
+ formDescriptionId: `${id}-form-item-description`,
61
+ formMessageId: `${id}-form-item-message`,
62
+ ...fieldState,
63
+ }
64
+ }
65
+
66
+ type FormItemContextValue = {
67
+ id: string
68
+ }
69
+
70
+ const FormItemContext = React.createContext<FormItemContextValue>(
71
+ {} as FormItemContextValue
72
+ )
73
+
74
+ function FormItem({ className, ...props }: React.ComponentProps<"div">) {
75
+ const id = React.useId()
76
+
77
+ return (
78
+ <FormItemContext.Provider value={{ id }}>
79
+ <div
80
+ data-slot="form-item"
81
+ className={cn("grid gap-2", className)}
82
+ {...props}
83
+ />
84
+ </FormItemContext.Provider>
85
+ )
86
+ }
87
+
88
+ function FormLabel({
89
+ className,
90
+ ...props
91
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
92
+ const { error, formItemId } = useFormField()
93
+
94
+ return (
95
+ <Label
96
+ data-slot="form-label"
97
+ data-error={!!error}
98
+ className={cn("data-[error=true]:text-destructive", className)}
99
+ htmlFor={formItemId}
100
+ {...props}
101
+ />
102
+ )
103
+ }
104
+
105
+ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
106
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
107
+
108
+ return (
109
+ <Slot
110
+ data-slot="form-control"
111
+ id={formItemId}
112
+ aria-describedby={
113
+ !error
114
+ ? `${formDescriptionId}`
115
+ : `${formDescriptionId} ${formMessageId}`
116
+ }
117
+ aria-invalid={!!error}
118
+ {...props}
119
+ />
120
+ )
121
+ }
122
+
123
+ function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
124
+ const { formDescriptionId } = useFormField()
125
+
126
+ return (
127
+ <p
128
+ data-slot="form-description"
129
+ id={formDescriptionId}
130
+ className={cn("text-muted-foreground text-sm", className)}
131
+ {...props}
132
+ />
133
+ )
134
+ }
135
+
136
+ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
137
+ const { error, formMessageId } = useFormField()
138
+ const body = error ? String(error?.message ?? "") : props.children
139
+
140
+ if (!body) {
141
+ return null
142
+ }
143
+
144
+ return (
145
+ <p
146
+ data-slot="form-message"
147
+ id={formMessageId}
148
+ className={cn("text-destructive text-sm", className)}
149
+ {...props}
150
+ >
151
+ {body}
152
+ </p>
153
+ )
154
+ }
155
+
156
+ export {
157
+ useFormField,
158
+ Form,
159
+ FormItem,
160
+ FormLabel,
161
+ FormControl,
162
+ FormDescription,
163
+ FormMessage,
164
+ FormField,
165
+ }
@@ -0,0 +1,86 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { FrostedPanel } from "./frosted-panel";
3
+
4
+ const meta: Meta<typeof FrostedPanel> = {
5
+ title: "UI/FrostedPanel",
6
+ component: FrostedPanel,
7
+ parameters: {
8
+ layout: "fullscreen",
9
+ },
10
+ tags: ["autodocs"],
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof FrostedPanel>;
15
+
16
+ export const Default: Story = {
17
+ args: {
18
+ children: (
19
+ <>
20
+ <FrostedPanel.Aside>
21
+ <div className="p-4 text-white">Aside Content</div>
22
+ </FrostedPanel.Aside>
23
+ <FrostedPanel.Main>
24
+ <div className="p-4">Main Content</div>
25
+ </FrostedPanel.Main>
26
+ </>
27
+ ),
28
+ },
29
+ };
30
+
31
+ export const CustomGradient: Story = {
32
+ args: {
33
+ gradientColors: {
34
+ top: "#1a237e",
35
+ bottom: "#0d47a1",
36
+ },
37
+ children: (
38
+ <>
39
+ <FrostedPanel.Aside>
40
+ <div className="p-4 text-white">Aside Content</div>
41
+ </FrostedPanel.Aside>
42
+ <FrostedPanel.Main>
43
+ <div className="p-4">Main Content</div>
44
+ </FrostedPanel.Main>
45
+ </>
46
+ ),
47
+ },
48
+ };
49
+
50
+ export const CustomBlobConfig: Story = {
51
+ args: {
52
+ blobConfig: {
53
+ count: 6,
54
+ color: "rgba(255, 255, 255, 0.6)",
55
+ minSize: 150,
56
+ maxSize: 300,
57
+ speed: 5,
58
+ },
59
+ children: (
60
+ <>
61
+ <FrostedPanel.Aside>
62
+ <div className="p-4 text-white">Aside Content</div>
63
+ </FrostedPanel.Aside>
64
+ <FrostedPanel.Main>
65
+ <div className="p-4">Main Content</div>
66
+ </FrostedPanel.Main>
67
+ </>
68
+ ),
69
+ },
70
+ };
71
+
72
+ export const CustomAsideWidth: Story = {
73
+ args: {
74
+ asideWidth: "40%",
75
+ children: (
76
+ <>
77
+ <FrostedPanel.Aside>
78
+ <div className="p-4 text-white">Wider Aside Content</div>
79
+ </FrostedPanel.Aside>
80
+ <FrostedPanel.Main>
81
+ <div className="p-4">Main Content</div>
82
+ </FrostedPanel.Main>
83
+ </>
84
+ ),
85
+ },
86
+ };
@@ -0,0 +1,276 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useRef, createContext, useContext, useState } from "react";
4
+ import { cn } from "@/lib/utils";
5
+ import logo from "../../assets/brand/saafe-color-white-logo.svg";
6
+ import { LanguageSwitcher } from "../language/LanguageSwitcher";
7
+ import { SessionTimer } from "../session/SessionTimer";
8
+ import { SessionTimeoutScreen } from "../session/SessionTimeoutScreen";
9
+ import { useRedirectStore } from "@/store/redirect.store";
10
+ import { useAuthStore } from "@/store/auth.store";
11
+ import { useTrustedCount } from "@/hooks/use-trusted-count";
12
+ import { VersionDisplay } from "./version-display";
13
+
14
+ // Create context for FrostedPanel configuration
15
+ type FrostedPanelContextType = {
16
+ asideWidth?: string | number;
17
+ asideRatio?: string;
18
+ gradientColors?: {
19
+ top: string;
20
+ bottom: string;
21
+ };
22
+ blobConfig?: {
23
+ count: number;
24
+ color: string;
25
+ minSize: number;
26
+ maxSize: number;
27
+ speed: number;
28
+ };
29
+ };
30
+
31
+ const FrostedPanelContext = createContext<FrostedPanelContextType>({
32
+ asideWidth: "30%",
33
+ asideRatio: "auto",
34
+ gradientColors: {
35
+ top: "#004d4d",
36
+ bottom: "#00b2c1",
37
+ },
38
+ blobConfig: {
39
+ count: 3,
40
+ color: "rgba(255, 255, 255, 0.4)",
41
+ minSize: 100,
42
+ maxSize: 250,
43
+ speed: 0.8,
44
+ },
45
+ });
46
+
47
+ interface FrostedPanelProps extends React.HTMLAttributes<HTMLDivElement> {
48
+ asideWidth?: string | number;
49
+ asideRatio?: string;
50
+ gradientColors?: {
51
+ top: string;
52
+ bottom: string;
53
+ };
54
+ blobConfig?: {
55
+ count: number;
56
+ color: string;
57
+ minSize: number;
58
+ maxSize: number;
59
+ speed: number;
60
+ };
61
+ }
62
+
63
+ function FrostedPanel({
64
+ children,
65
+ className,
66
+ asideWidth = "30%",
67
+ asideRatio = "auto",
68
+ gradientColors = {
69
+ top: "#004d4d",
70
+ bottom: "#00b2c1",
71
+ },
72
+ blobConfig = {
73
+ count: 4,
74
+ color: "rgba(255, 255, 255, 0.4)",
75
+ minSize: 100,
76
+ maxSize: 250,
77
+ speed: 10,
78
+ },
79
+ ...props
80
+ }: FrostedPanelProps) {
81
+ const contextValue = {
82
+ asideWidth,
83
+ asideRatio,
84
+ gradientColors,
85
+ blobConfig,
86
+ };
87
+
88
+ return (
89
+ <FrostedPanelContext.Provider value={contextValue}>
90
+ <div className={cn("flex w-full h-screen overflow-hidden", className)} {...props}>
91
+ {children}
92
+ </div>
93
+ </FrostedPanelContext.Provider>
94
+ );
95
+ }
96
+
97
+ interface AsideProps extends React.HTMLAttributes<HTMLDivElement> {
98
+ width?: string | number;
99
+ ratio?: string;
100
+ }
101
+
102
+ function Aside({ children, className, width, ratio, ...props }: AsideProps) {
103
+ const canvasRef = useRef<HTMLCanvasElement>(null);
104
+ const animationRef = useRef<number | undefined>(undefined);
105
+ const currentPath = location.pathname;
106
+ const isLoginPage = currentPath?.split('/')?.includes('login');
107
+ const blobsRef = useRef<Array<{
108
+ x: number;
109
+ y: number;
110
+ radius: number;
111
+ xSpeed: number;
112
+ ySpeed: number;
113
+ opacity: number;
114
+ }>>([]);
115
+ const [isSessionExpired, setIsSessionExpired] = useState(false);
116
+ const { getCustomStyle } = useRedirectStore();
117
+ const customStyles = getCustomStyle();
118
+ const context = useContext(FrostedPanelContext);
119
+ const { isAuthenticated } = useAuthStore();
120
+ const { data: trustedCount, isLoading } = useTrustedCount();
121
+ console.log(trustedCount);
122
+
123
+
124
+ // Handle session timeout - only called when timer actually reaches zero
125
+ const handleTimeout = () => {
126
+ setIsSessionExpired(true);
127
+ };
128
+
129
+ useEffect(() => {
130
+ const canvas = canvasRef.current;
131
+ if (!canvas) return;
132
+
133
+ const ctx = canvas.getContext("2d");
134
+ if (!ctx) return;
135
+
136
+ // Initialize blobs only once
137
+ if (blobsRef.current.length === 0) {
138
+ const blobConfig = context.blobConfig;
139
+ for (let i = 0; i < (blobConfig?.count || 3); i++) {
140
+ blobsRef.current.push({
141
+ x: Math.random() * canvas.width,
142
+ y: Math.random() * canvas.height,
143
+ radius: blobConfig?.minSize || 100 + Math.random() * ((blobConfig?.maxSize || 250) - (blobConfig?.minSize || 100)),
144
+ xSpeed: (Math.random() - 0.5) * (blobConfig?.speed || 0.8),
145
+ ySpeed: (Math.random() - 0.5) * (blobConfig?.speed || 0.8),
146
+ opacity: 0.4 + Math.random() * 0.4,
147
+ });
148
+ }
149
+ }
150
+
151
+ const resizeCanvas = () => {
152
+ const parent = canvas.parentElement;
153
+ if (!parent) return;
154
+ canvas.width = parent.offsetWidth;
155
+ canvas.height = parent.offsetHeight;
156
+ };
157
+
158
+ resizeCanvas();
159
+ window.addEventListener("resize", resizeCanvas);
160
+
161
+ const animate = () => {
162
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
163
+ blobsRef.current.forEach((blob) => {
164
+ blob.x += blob.xSpeed;
165
+ blob.y += blob.ySpeed;
166
+
167
+ if (blob.x < -blob.radius || blob.x > canvas.width + blob.radius) {
168
+ blob.xSpeed = -blob.xSpeed;
169
+ }
170
+ if (blob.y < -blob.radius || blob.y > canvas.height + blob.radius) {
171
+ blob.ySpeed = -blob.ySpeed;
172
+ }
173
+
174
+ ctx.beginPath();
175
+ ctx.arc(blob.x, blob.y, blob.radius, 0, Math.PI * 2);
176
+ ctx.fillStyle = context.blobConfig?.color
177
+ .replace(")", `, ${blob.opacity})`)
178
+ .replace("rgba", "rgba")
179
+ .replace("rgb", "rgba") || "";
180
+ ctx.fill();
181
+ });
182
+
183
+ animationRef.current = requestAnimationFrame(animate);
184
+ };
185
+
186
+ animate();
187
+
188
+ return () => {
189
+ window.removeEventListener("resize", resizeCanvas);
190
+ if (animationRef.current) {
191
+ cancelAnimationFrame(animationRef.current);
192
+ }
193
+ };
194
+ }, []); // Empty dependency array to run only once
195
+
196
+ if (isSessionExpired) {
197
+ return <SessionTimeoutScreen />;
198
+ }
199
+
200
+ return (
201
+ <div
202
+ className={cn(
203
+ "relative flex flex-col justify-center overflow-y-auto [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden",
204
+ className
205
+ )}
206
+ style={{
207
+ width: width || context.asideWidth,
208
+ aspectRatio: ratio || context.asideRatio,
209
+ background: context.gradientColors
210
+ ? `linear-gradient(to bottom, ${customStyles?.primaryButtonColor || context.gradientColors.top}, ${customStyles?.primaryButtonColor ? `${customStyles?.primaryButtonColor}95` : context.gradientColors.bottom})`
211
+ : undefined,
212
+ }}
213
+ {...props}
214
+ >
215
+ <canvas ref={canvasRef} className="absolute inset-0 z-10 w-full h-full" />
216
+ <div className="absolute inset-0 backdrop-blur-[140px] bg-white/10 z-20" />
217
+ <div className="z-50 mx-6 mt-4">
218
+ {isLoginPage ? (
219
+ <LanguageSwitcher />
220
+ ) : (
221
+ <SessionTimer onTimeout={handleTimeout} />
222
+ )}
223
+ </div>
224
+ <div className="relative z-30 p-6 flex-1 flex flex-col overflow-y-auto max-h-screen [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
225
+ <div className="pb-8">
226
+ {children}
227
+ </div>
228
+ </div>
229
+ <div className="z-50 ml-4 mt-4">
230
+ <div className="flex justify-start mb-4">
231
+ <VersionDisplay variant="minimal"showIcon={true} />
232
+ </div>
233
+ <div className="w-80 h-30 bg-black/15 rounded-tr-full rounded-br-4xl absolute left-0 bottom-0 blur-2xl -z-10" />
234
+ {isAuthenticated ?
235
+ <>
236
+ <div className="text-white text-lg flex items-center gap-2 z-auto">
237
+ <p>Powered by</p>
238
+ <div className="flex flex-row gap-1 items-end">
239
+ <img src={logo} alt="Saafe Logo" className="w-20 h-auto mb-1" />
240
+ </div>
241
+ </div>
242
+ {!isLoading && trustedCount?.displayText && (
243
+ <p className="text-surface font-light text-md mt-0.5">{trustedCount.displayText}</p>
244
+ )}
245
+ </>
246
+ :
247
+ <>
248
+ <div className="text-surface text-sm flex items-center gap-2 z-50">
249
+ <p>Powered by RBI regulated AA</p>
250
+ </div>
251
+ <div className="flex flex-row gap-1 items-end">
252
+ <img src={logo} alt="Saafe Logo" className="w-22 h-auto mt-0.5" />
253
+ </div>
254
+ </>
255
+ }
256
+ </div>
257
+ </div>
258
+ );
259
+ }
260
+
261
+ function Main({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
262
+ return (
263
+ <div
264
+ className={cn("flex-1 flex flex-col overflow-y-auto", className)}
265
+ {...props}
266
+ >
267
+ {children}
268
+ </div>
269
+ );
270
+ }
271
+
272
+ // Assign components to FrostedPanel
273
+ FrostedPanel.Aside = Aside;
274
+ FrostedPanel.Main = Main;
275
+
276
+ export { FrostedPanel };
@@ -0,0 +1,39 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface InputProps extends React.ComponentProps<"input"> {
5
+ leftSection?: React.ReactNode;
6
+ rightSection?: React.ReactNode;
7
+ }
8
+
9
+ function Input({ className, type, leftSection, rightSection, ...props }: InputProps) {
10
+ return (
11
+ <div className="relative w-full">
12
+ {leftSection && (
13
+ <div className="absolute left-3 top-1/2 -translate-y-1/2 z-10">
14
+ {leftSection}
15
+ </div>
16
+ )}
17
+ {rightSection && (
18
+ <div className="absolute right-3 top-1/2 -translate-y-1/2 z-10">
19
+ {rightSection}
20
+ </div>
21
+ )}
22
+ <input
23
+ type={type}
24
+ data-slot="input"
25
+ className={cn(
26
+ "dark:text-white file:text-consent-primary placeholder:text-consent-secondary selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-md",
27
+ "focus-visible:border-primary focus-visible:ring-ring/50 focus-visible:ring-[3px]",
28
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
29
+ leftSection && "pl-10",
30
+ rightSection && "pr-10",
31
+ className
32
+ )}
33
+ {...props}
34
+ />
35
+ </div>
36
+ )
37
+ }
38
+
39
+ export { Input }
@@ -0,0 +1,67 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Label } from "./label";
3
+
4
+ const meta: Meta<typeof Label> = {
5
+ title: "UI/Label",
6
+ component: Label,
7
+ parameters: {
8
+ layout: "padded",
9
+ },
10
+ tags: ["autodocs"],
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof Label>;
15
+
16
+ export const Default: Story = {
17
+ args: {
18
+ children: "Default Label",
19
+ },
20
+ };
21
+
22
+ export const WithHtmlFor: Story = {
23
+ args: {
24
+ htmlFor: "input-id",
25
+ children: "Label with htmlFor",
26
+ },
27
+ };
28
+
29
+ export const CustomClassName: Story = {
30
+ args: {
31
+ className: "text-lg text-primary",
32
+ children: "Custom Styled Label",
33
+ },
34
+ };
35
+
36
+ export const Disabled: Story = {
37
+ args: {
38
+ className: "group-data-[disabled=true]:opacity-50",
39
+ children: "Disabled Label",
40
+ },
41
+ };
42
+
43
+ export const WithIcon: Story = {
44
+ args: {
45
+ className: "flex items-center gap-2",
46
+ children: (
47
+ <>
48
+ <span>Label with Icon</span>
49
+ <svg
50
+ xmlns="http://www.w3.org/2000/svg"
51
+ width="16"
52
+ height="16"
53
+ viewBox="0 0 24 24"
54
+ fill="none"
55
+ stroke="currentColor"
56
+ strokeWidth="2"
57
+ strokeLinecap="round"
58
+ strokeLinejoin="round"
59
+ >
60
+ <circle cx="12" cy="12" r="10" />
61
+ <line x1="12" y1="8" x2="12" y2="12" />
62
+ <line x1="12" y1="16" x2="12.01" y2="16" />
63
+ </svg>
64
+ </>
65
+ ),
66
+ },
67
+ };
@@ -0,0 +1,23 @@
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { useRTL } from "@/contexts/RTLContext"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ function Label({
7
+ className,
8
+ ...props
9
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
10
+ const { isRTL } = useRTL();
11
+ return (
12
+ <LabelPrimitive.Root
13
+ data-slot="label"
14
+ className={cn(
15
+ `flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50 ${isRTL ? 'flex-row-reverse' : ''}`,
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ export { Label }