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,259 @@
1
+ "use client";
2
+
3
+ import { cn } from "@/lib/utils";
4
+ import { LoaderCircle } from "lucide-react";
5
+ import * as React from "react";
6
+ import { createContext, useContext } from "react";
7
+ import { CheckIcon } from "@radix-ui/react-icons";
8
+
9
+ // Types
10
+ type StepperContextValue = {
11
+ activeStep: number;
12
+ setActiveStep: (step: number) => void;
13
+ orientation: "horizontal" | "vertical";
14
+ };
15
+
16
+ type StepItemContextValue = {
17
+ step: number;
18
+ state: StepState;
19
+ isDisabled: boolean;
20
+ isLoading: boolean;
21
+ };
22
+
23
+ type StepState = "active" | "completed" | "inactive" | "loading";
24
+
25
+ // Contexts
26
+ const StepperContext = createContext<StepperContextValue | undefined>(undefined);
27
+ const StepItemContext = createContext<StepItemContextValue | undefined>(undefined);
28
+
29
+ const useStepper = () => {
30
+ const context = useContext(StepperContext);
31
+ if (!context) {
32
+ throw new Error("useStepper must be used within a Stepper");
33
+ }
34
+ return context;
35
+ };
36
+
37
+ const useStepItem = () => {
38
+ const context = useContext(StepItemContext);
39
+ if (!context) {
40
+ throw new Error("useStepItem must be used within a StepperItem");
41
+ }
42
+ return context;
43
+ };
44
+
45
+ // Components
46
+ interface StepperProps extends React.HTMLAttributes<HTMLDivElement> {
47
+ defaultValue?: number;
48
+ value?: number;
49
+ onValueChange?: (value: number) => void;
50
+ orientation?: "horizontal" | "vertical";
51
+ }
52
+
53
+ const Stepper = React.forwardRef<HTMLDivElement, StepperProps>(
54
+ (
55
+ { defaultValue = 0, value, onValueChange, orientation = "horizontal", className, ...props },
56
+ ref,
57
+ ) => {
58
+ const [activeStep, setInternalStep] = React.useState(defaultValue);
59
+
60
+ const setActiveStep = React.useCallback(
61
+ (step: number) => {
62
+ if (value === undefined) {
63
+ setInternalStep(step);
64
+ }
65
+ onValueChange?.(step);
66
+ },
67
+ [value, onValueChange],
68
+ );
69
+
70
+ const currentStep = value ?? activeStep;
71
+
72
+ return (
73
+ <StepperContext.Provider
74
+ value={{
75
+ activeStep: currentStep,
76
+ setActiveStep,
77
+ orientation,
78
+ }}
79
+ >
80
+ <div
81
+ ref={ref}
82
+ className={cn(
83
+ "group/stepper inline-flex data-[orientation=horizontal]:w-full data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col",
84
+ className,
85
+ )}
86
+ data-orientation={orientation}
87
+ {...props}
88
+ />
89
+ </StepperContext.Provider>
90
+ );
91
+ },
92
+ );
93
+ Stepper.displayName = "Stepper";
94
+
95
+ // StepperItem
96
+ interface StepperItemProps extends React.HTMLAttributes<HTMLDivElement> {
97
+ step: number;
98
+ completed?: boolean;
99
+ disabled?: boolean;
100
+ loading?: boolean;
101
+ }
102
+
103
+ const StepperItem = React.forwardRef<HTMLDivElement, StepperItemProps>(
104
+ (
105
+ { step, completed = false, disabled = false, loading = false, className, children, ...props },
106
+ ref,
107
+ ) => {
108
+ const { activeStep } = useStepper();
109
+
110
+ const state: StepState =
111
+ completed || step < activeStep ? "completed" : activeStep === step ? "active" : "inactive";
112
+
113
+ const isLoading = loading && step === activeStep;
114
+
115
+ return (
116
+ <StepItemContext.Provider value={{ step, state, isDisabled: disabled, isLoading }}>
117
+ <div
118
+ ref={ref}
119
+ className={cn(
120
+ "group/step flex items-center group-data-[orientation=horizontal]/stepper:flex-row group-data-[orientation=vertical]/stepper:flex-col",
121
+ className,
122
+ )}
123
+ data-state={state}
124
+ {...(isLoading ? { "data-loading": true } : {})}
125
+ {...props}
126
+ >
127
+ {children}
128
+ </div>
129
+ </StepItemContext.Provider>
130
+ );
131
+ },
132
+ );
133
+ StepperItem.displayName = "StepperItem";
134
+
135
+ // StepperTrigger
136
+ interface StepperTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
137
+ asChild?: boolean;
138
+ }
139
+
140
+ const StepperTrigger = React.forwardRef<HTMLButtonElement, StepperTriggerProps>(
141
+ ({ asChild = false, className, children, ...props }, ref) => {
142
+ const { setActiveStep } = useStepper();
143
+ const { step, isDisabled } = useStepItem();
144
+
145
+ if (asChild) {
146
+ return <div className={className}>{children}</div>;
147
+ }
148
+
149
+ return (
150
+ <button
151
+ ref={ref}
152
+ className={cn(
153
+ "inline-flex items-center gap-3 disabled:pointer-events-none disabled:opacity-50",
154
+ className,
155
+ )}
156
+ onClick={() => setActiveStep(step)}
157
+ disabled={isDisabled}
158
+ {...props}
159
+ >
160
+ {children}
161
+ </button>
162
+ );
163
+ },
164
+ );
165
+ StepperTrigger.displayName = "StepperTrigger";
166
+
167
+ // StepperIndicator
168
+ interface StepperIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
169
+ asChild?: boolean;
170
+ }
171
+
172
+ const StepperIndicator = React.forwardRef<HTMLDivElement, StepperIndicatorProps>(
173
+ ({ asChild = false, className, children, ...props }, ref) => {
174
+ const { state, step, isLoading } = useStepItem();
175
+
176
+ return (
177
+ <div
178
+ ref={ref}
179
+ className={cn(
180
+ "relative flex size-6 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground data-[state=active]:bg-primary data-[state=completed]:bg-primary data-[state=active]:text-primary-foreground data-[state=completed]:text-primary-foreground",
181
+ className,
182
+ )}
183
+ data-state={state}
184
+ {...props}
185
+ >
186
+ {asChild ? (
187
+ children
188
+ ) : (
189
+ <>
190
+ <span className="transition-all group-data-[loading=true]/step:scale-0 group-data-[state=completed]/step:scale-0 group-data-[loading=true]/step:opacity-0 group-data-[state=completed]/step:opacity-0 group-data-[loading=true]/step:transition-none">
191
+ {step}
192
+ </span>
193
+ <CheckIcon
194
+ className="absolute scale-0 opacity-0 transition-all group-data-[state=completed]/step:scale-100 group-data-[state=completed]/step:opacity-100"
195
+ width={16}
196
+ height={16}
197
+ aria-hidden="true"
198
+ />
199
+ {isLoading && (
200
+ <span className="absolute transition-all">
201
+ <LoaderCircle
202
+ className="animate-spin"
203
+ size={14}
204
+ strokeWidth={2}
205
+ aria-hidden="true"
206
+ />
207
+ </span>
208
+ )}
209
+ </>
210
+ )}
211
+ </div>
212
+ );
213
+ },
214
+ );
215
+ StepperIndicator.displayName = "StepperIndicator";
216
+
217
+ // StepperTitle
218
+ const StepperTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
219
+ ({ className, ...props }, ref) => (
220
+ <h3 ref={ref} className={cn("text-sm font-medium", className)} {...props} />
221
+ ),
222
+ );
223
+ StepperTitle.displayName = "StepperTitle";
224
+
225
+ // StepperDescription
226
+ const StepperDescription = React.forwardRef<
227
+ HTMLParagraphElement,
228
+ React.HTMLAttributes<HTMLParagraphElement>
229
+ >(({ className, ...props }, ref) => (
230
+ <p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
231
+ ));
232
+ StepperDescription.displayName = "StepperDescription";
233
+
234
+ // StepperSeparator
235
+ const StepperSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
236
+ ({ className, ...props }, ref) => {
237
+ return (
238
+ <div
239
+ ref={ref}
240
+ className={cn(
241
+ "m-0.5 bg-muted group-data-[orientation=horizontal]/stepper:h-0.5 group-data-[orientation=vertical]/stepper:h-12 group-data-[orientation=horizontal]/stepper:w-full group-data-[orientation=vertical]/stepper:w-0.5 group-data-[orientation=horizontal]/stepper:flex-1 group-data-[state=completed]/step:bg-primary",
242
+ className,
243
+ )}
244
+ {...props}
245
+ />
246
+ );
247
+ },
248
+ );
249
+ StepperSeparator.displayName = "StepperSeparator";
250
+
251
+ export {
252
+ Stepper,
253
+ StepperDescription,
254
+ StepperIndicator,
255
+ StepperItem,
256
+ StepperSeparator,
257
+ StepperTitle,
258
+ StepperTrigger,
259
+ };
@@ -0,0 +1,55 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Tabs = TabsPrimitive.Root
9
+
10
+ const TabsList = React.forwardRef<
11
+ React.ElementRef<typeof TabsPrimitive.List>,
12
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
13
+ >(({ className, ...props }, ref) => (
14
+ <TabsPrimitive.List
15
+ ref={ref}
16
+ className={cn(
17
+ "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ ))
23
+ TabsList.displayName = TabsPrimitive.List.displayName
24
+
25
+ const TabsTrigger = React.forwardRef<
26
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
27
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
28
+ >(({ className, ...props }, ref) => (
29
+ <TabsPrimitive.Trigger
30
+ ref={ref}
31
+ className={cn(
32
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
33
+ className
34
+ )}
35
+ {...props}
36
+ />
37
+ ))
38
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39
+
40
+ const TabsContent = React.forwardRef<
41
+ React.ElementRef<typeof TabsPrimitive.Content>,
42
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
43
+ >(({ className, ...props }, ref) => (
44
+ <TabsPrimitive.Content
45
+ ref={ref}
46
+ className={cn(
47
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ ))
53
+ TabsContent.displayName = TabsPrimitive.Content.displayName
54
+
55
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -0,0 +1,61 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function TooltipProvider({
9
+ delayDuration = 0,
10
+ ...props
11
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
12
+ return (
13
+ <TooltipPrimitive.Provider
14
+ data-slot="tooltip-provider"
15
+ delayDuration={delayDuration}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ function Tooltip({
22
+ ...props
23
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
24
+ return (
25
+ <TooltipProvider>
26
+ <TooltipPrimitive.Root data-slot="tooltip" {...props} />
27
+ </TooltipProvider>
28
+ )
29
+ }
30
+
31
+ function TooltipTrigger({
32
+ ...props
33
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
34
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
35
+ }
36
+
37
+ function TooltipContent({
38
+ className,
39
+ sideOffset = 0,
40
+ children,
41
+ ...props
42
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
43
+ return (
44
+ <TooltipPrimitive.Portal>
45
+ <TooltipPrimitive.Content
46
+ data-slot="tooltip-content"
47
+ sideOffset={sideOffset}
48
+ className={cn(
49
+ "bg-gray-700 text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
50
+ className
51
+ )}
52
+ {...props}
53
+ >
54
+ {children}
55
+ <TooltipPrimitive.Arrow className="bg-gray-700 fill-gray-700 z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
56
+ </TooltipPrimitive.Content>
57
+ </TooltipPrimitive.Portal>
58
+ )
59
+ }
60
+
61
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -0,0 +1,36 @@
1
+ import logo from "@/assets/brand/saafe-icon.svg";
2
+
3
+ interface UrlLoaderProps {
4
+ message?: string;
5
+ isError?: boolean;
6
+ isWarning?: boolean;
7
+ }
8
+
9
+ export function UrlDecodeLoader({ message = "Loading...", isError = false, isWarning = false }: UrlLoaderProps) {
10
+ return (
11
+ <div className="flex flex-col items-center justify-center h-screen w-full">
12
+ <div className="flex flex-col items-center gap-6">
13
+ <img src={logo} alt="Saafe Logo" className="h-16 w-16" />
14
+
15
+ {isError ? (
16
+ <div className="flex flex-col items-center gap-4">
17
+ <p className="text-red-500 font-medium text-lg">Error</p>
18
+ <p className="text-gray-700 dark:text-gray-300">{message}</p>
19
+ </div>
20
+ ) : isWarning ? (
21
+ <div className="flex flex-col items-center gap-4">
22
+ <p className="text-yellow-500 font-medium text-lg">No Consent handler found</p>
23
+ <p className="text-gray-700 dark:text-gray-300">{message}</p>
24
+ </div>
25
+ ) : (
26
+ <div className="flex flex-col items-center gap-4">
27
+ <div className="flex items-center justify-center">
28
+ <div className="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
29
+ </div>
30
+ <p className="text-gray-700 dark:text-gray-300">{message}</p>
31
+ </div>
32
+ )}
33
+ </div>
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,104 @@
1
+ import { cn } from "@/lib/utils";
2
+ import { Badge } from "@/components/ui/badge";
3
+ import { Info } from "lucide-react";
4
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
5
+ import { useMediaQuery } from "@/hooks/use-media-query";
6
+ import { getCurrentEnvironment, getEnvironmentConfig } from "@/config/environments";
7
+
8
+ interface VersionDisplayProps {
9
+ className?: string;
10
+ variant?: "default" | "footer" | "badge" | "minimal";
11
+ showIcon?: boolean;
12
+ showLabel?: boolean;
13
+ }
14
+
15
+ export function VersionDisplay({
16
+ className,
17
+ variant = "default",
18
+ showIcon = true,
19
+ showLabel = true
20
+ }: VersionDisplayProps) {
21
+ // Get version from package.json
22
+ const version = import.meta.env.VITE_APP_VERSION || "1.1.0";
23
+ const buildDate = import.meta.env.VITE_BUILD_DATE || new Date().toISOString().split('T')[0];
24
+ const environment = getCurrentEnvironment();
25
+ const config = getEnvironmentConfig();
26
+ const isMobile = useMediaQuery("(max-width: 768px)");
27
+
28
+
29
+ const VersionContent = () => {
30
+ switch (variant) {
31
+ case "footer":
32
+ return (
33
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
34
+ {showIcon && <Info className="h-3 w-3" />}
35
+ <span>v{version}</span>
36
+ </div>
37
+ );
38
+
39
+ case "badge":
40
+ return (
41
+ <Badge variant="outline" className="text-xs">
42
+ {showIcon && <Info className="h-3 w-3 mr-1" />}
43
+ v{version}
44
+ </Badge>
45
+ );
46
+
47
+ case "minimal":
48
+ return (
49
+ <span className={cn("text-xs text-muted-foreground", isMobile ? "text-muted-secondary font-light" : "text-white")}>
50
+ v{version} {environment.toLowerCase() !== 'production' && environment !== "" ? `| ${environment.toUpperCase()}` : null}
51
+ </span>
52
+ );
53
+
54
+ default:
55
+ return (
56
+ <div className="flex items-center gap-2 text-sm">
57
+ {showIcon && <Info className="h-4 w-4 text-muted-foreground" />}
58
+ <div className="flex flex-col sm:flex-row sm:items-center sm:gap-2">
59
+ {showLabel && <span className="text-muted-foreground">Version:</span>}
60
+ <span className="font-medium">v{version}</span>
61
+ <span className="text-xs text-muted-foreground hidden sm:inline">
62
+ ({buildDate})
63
+ </span>
64
+ </div>
65
+ </div>
66
+ );
67
+ }
68
+ };
69
+
70
+ const tooltipContent = (
71
+ <div className="min-w-48">
72
+ <div className="text-center mb-2">
73
+ <div className="text-sm font-medium">Version {version}</div>
74
+ </div>
75
+ <div className="flex flex-col gap-1">
76
+ <div className="text-xs text-white">Released on {buildDate}</div>
77
+ {
78
+ environment !== "production" && (
79
+ <>
80
+ <div className="text-xs text-white">Environment: {config.name}</div>
81
+ <div className="text-xs text-white">API: {config.apiBaseUrl}</div>
82
+ {config.debug && <div className="text-xs text-yellow-500">Debug Mode Enabled</div>}
83
+ </>
84
+ )
85
+ }
86
+ </div>
87
+ </div>
88
+ );
89
+
90
+ return (
91
+ <TooltipProvider>
92
+ <Tooltip>
93
+ <TooltipTrigger asChild>
94
+ <div className={cn("cursor-help", className)}>
95
+ <VersionContent />
96
+ </div>
97
+ </TooltipTrigger>
98
+ <TooltipContent>
99
+ {tooltipContent}
100
+ </TooltipContent>
101
+ </Tooltip>
102
+ </TooltipProvider>
103
+ );
104
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import { AnimatePresence, motion } from 'framer-motion'
3
+ import { useMediaQuery } from '@/hooks/use-media-query';
4
+ import { useRTL } from '@/contexts/RTLContext';
5
+ const WebFooter = ({
6
+ show,
7
+ children
8
+ }: {
9
+ show: boolean;
10
+ children: React.ReactNode;
11
+ }) => {
12
+ const isMobile = useMediaQuery("(max-width: 768px)");
13
+ const { isRTL } = useRTL();
14
+ return (
15
+ <div className='w-full'>
16
+ <AnimatePresence>
17
+ {(show && !isMobile) ? (
18
+ <motion.div
19
+ key={"desktop"}
20
+ initial={{ y: 100, opacity: 0 }}
21
+ animate={{ y: 0, opacity: 1 }}
22
+ exit={{ y: 100, opacity: 0 }}
23
+ transition={{ duration: 0.3 }}
24
+ className={`${isRTL ? 'w-[70%] right-[30%]' : 'w-[70%] left-[30%]'} fixed bottom-0`}
25
+ >
26
+ <div className='drop-shadow-[0_35px_35px_rgba(0,0,0,0.25)] flex justify-end w-full px-14 gap-4 py-4 items-end bg-white dark:bg-card'>
27
+ {children}
28
+ </div>
29
+ </motion.div>
30
+ ) : null}
31
+ </AnimatePresence>
32
+ </div>
33
+ )
34
+ }
35
+
36
+ export default WebFooter
@@ -0,0 +1,99 @@
1
+ export interface EnvironmentConfig {
2
+ name: string;
3
+ apiBaseUrl: string;
4
+ debug: boolean;
5
+ analyticsEnabled: boolean;
6
+ features: {
7
+ [key: string]: boolean;
8
+ };
9
+ }
10
+
11
+ export const environments: Record<string, EnvironmentConfig> = {
12
+ production: {
13
+ name: 'Production',
14
+ apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
15
+ debug: false,
16
+ analyticsEnabled: true,
17
+ features: {
18
+ debugPanel: false,
19
+ testMode: false,
20
+ mockData: false,
21
+ },
22
+ },
23
+ stage: {
24
+ name: 'Stage',
25
+ apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
26
+ debug: true,
27
+ analyticsEnabled: true,
28
+ features: {
29
+ debugPanel: true,
30
+ testMode: true,
31
+ mockData: false,
32
+ },
33
+ },
34
+ sandbox: {
35
+ name: 'Sandbox',
36
+ apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
37
+ debug: true,
38
+ analyticsEnabled: false,
39
+ features: {
40
+ debugPanel: true,
41
+ testMode: true,
42
+ mockData: true,
43
+ },
44
+ },
45
+ development: {
46
+ name: 'Development',
47
+ apiBaseUrl: 'http://localhost:3000',
48
+ debug: true,
49
+ analyticsEnabled: false,
50
+ features: {
51
+ debugPanel: true,
52
+ testMode: true,
53
+ mockData: true,
54
+ },
55
+ },
56
+ };
57
+
58
+ // Get current environment based on various indicators
59
+ export function getCurrentEnvironment(): string {
60
+ // Check for explicit environment variable
61
+ if (import.meta.env.VITE_ENVIRONMENT) {
62
+ return import.meta.env.VITE_ENVIRONMENT;
63
+ }
64
+
65
+ // Check build mode
66
+ if (import.meta.env.MODE) {
67
+ return import.meta.env.MODE;
68
+ }
69
+
70
+ // Check hostname in browser
71
+ if (typeof window !== 'undefined') {
72
+ const hostname = window.location.hostname;
73
+
74
+ if (hostname.includes('localhost') || hostname.includes('127.0.0.1')) {
75
+ return 'development';
76
+ } else if (hostname.includes('sandbox')) {
77
+ return 'sandbox';
78
+ } else if (hostname.includes('stage') || hostname.includes('staging')) {
79
+ return 'stage';
80
+ } else {
81
+ return 'production';
82
+ }
83
+ }
84
+
85
+ // Default to production
86
+ return 'production';
87
+ }
88
+
89
+ // Get configuration for current environment
90
+ export function getEnvironmentConfig(): EnvironmentConfig {
91
+ const currentEnv = getCurrentEnvironment();
92
+ return environments[currentEnv] || environments.production;
93
+ }
94
+
95
+ // Helper to check if feature is enabled
96
+ export function isFeatureEnabled(feature: string): boolean {
97
+ const config = getEnvironmentConfig();
98
+ return config.features[feature] || false;
99
+ }