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,50 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Skeleton } from "./skeleton";
3
+
4
+ const meta: Meta<typeof Skeleton> = {
5
+ title: "UI/Skeleton",
6
+ component: Skeleton,
7
+ parameters: {
8
+ layout: "padded",
9
+ },
10
+ tags: ["autodocs"],
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof Skeleton>;
15
+
16
+ export const Default: Story = {
17
+ args: {
18
+ className: "h-4 w-[250px]",
19
+ },
20
+ };
21
+
22
+ export const Circle: Story = {
23
+ args: {
24
+ className: "h-12 w-12 rounded-full",
25
+ },
26
+ };
27
+
28
+ export const Card: Story = {
29
+ args: {
30
+ className: "h-[200px] w-[300px] rounded-lg",
31
+ },
32
+ };
33
+
34
+ export const Text: Story = {
35
+ args: {
36
+ className: "h-4 w-[200px]",
37
+ },
38
+ };
39
+
40
+ export const Avatar: Story = {
41
+ args: {
42
+ className: "h-10 w-10 rounded-full",
43
+ },
44
+ };
45
+
46
+ export const CustomColor: Story = {
47
+ args: {
48
+ className: "h-4 w-[250px] bg-gray-300",
49
+ },
50
+ };
@@ -0,0 +1,15 @@
1
+ import { cn } from "@/lib/utils"
2
+
3
+ function Skeleton({
4
+ className,
5
+ ...props
6
+ }: React.HTMLAttributes<HTMLDivElement>) {
7
+ return (
8
+ <div
9
+ className={cn("animate-pulse rounded-md bg-primary/10", className)}
10
+ {...props}
11
+ />
12
+ )
13
+ }
14
+
15
+ export { Skeleton }
@@ -0,0 +1,23 @@
1
+ import { useTheme } from "next-themes"
2
+ import { Toaster as Sonner, ToasterProps } from "sonner"
3
+
4
+ const Toaster = ({ ...props }: ToasterProps) => {
5
+ const { theme = "system" } = useTheme()
6
+
7
+ return (
8
+ <Sonner
9
+ theme={theme as ToasterProps["theme"]}
10
+ className="toaster group"
11
+ style={
12
+ {
13
+ "--normal-bg": "var(--popover)",
14
+ "--normal-text": "var(--popover-foreground)",
15
+ "--normal-border": "var(--border)",
16
+ } as React.CSSProperties
17
+ }
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ export { Toaster }
@@ -0,0 +1,132 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Step, StepGroup, StepConnector } from "./step";
3
+ import { Link, Shield, FileText } from "lucide-react";
4
+ import { useState, useEffect } from "react";
5
+
6
+ const meta: Meta<typeof Step> = {
7
+ title: "UI/Step",
8
+ component: Step,
9
+ parameters: {
10
+ layout: "centered",
11
+ },
12
+ tags: ["autodocs"],
13
+ };
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof Step>;
17
+
18
+ // Example of a step with dynamic child steps
19
+ const StepWithDynamicChildren = () => {
20
+ const [childSteps, setChildSteps] = useState<Array<{
21
+ id: string;
22
+ title: string;
23
+ description: string;
24
+ isActive: boolean;
25
+ isCompleted: boolean;
26
+ }>>([]);
27
+
28
+ useEffect(() => {
29
+ // Simulate API fetch
30
+ const fetchedSteps = [
31
+ {
32
+ id: "bank-1",
33
+ title: "Bank Accounts",
34
+ description: "Link your bank accounts",
35
+ isActive: true,
36
+ isCompleted: false,
37
+ },
38
+ {
39
+ id: "insurance-1",
40
+ title: "Insurance",
41
+ description: "Link your insurance policies",
42
+ isActive: false,
43
+ isCompleted: false,
44
+ },
45
+ {
46
+ id: "investment-1",
47
+ title: "Investments",
48
+ description: "Link your investment accounts",
49
+ isActive: false,
50
+ isCompleted: false,
51
+ },
52
+ ];
53
+
54
+ // Simulate delayed loading of child steps
55
+ setTimeout(() => {
56
+ setChildSteps(fetchedSteps);
57
+ }, 1000);
58
+ }, []);
59
+
60
+ return (
61
+ <div className="w-[600px] p-8 bg-gray-100 rounded-lg">
62
+ <StepGroup activeStep={2} completedSteps={[1]} baseColor="#004d4d" spacing={32}>
63
+ <div className="relative">
64
+ <Step
65
+ step={1}
66
+ title="First Step"
67
+ description="This is the first step"
68
+ icon={<Shield className="w-4 h-4" />}
69
+ isActive={false}
70
+ isCompleted={true}
71
+ />
72
+ <StepConnector isActive={false} isCompleted={true} />
73
+ </div>
74
+ <div className="relative">
75
+ <Step
76
+ step={2}
77
+ title="Link Accounts"
78
+ description="Select & Link the accounts you'd like to share"
79
+ icon={<Link className="w-4 h-4" />}
80
+ isActive={true}
81
+ isCompleted={false}
82
+ childSteps={childSteps}
83
+ />
84
+ <StepConnector isActive={true} isCompleted={false} />
85
+ </div>
86
+ <div className="relative">
87
+ <Step
88
+ step={3}
89
+ title="Review Consent"
90
+ description="Review and approve the consent"
91
+ icon={<FileText className="w-4 h-4" />}
92
+ isActive={false}
93
+ isCompleted={false}
94
+ isLast={true}
95
+ />
96
+ </div>
97
+ </StepGroup>
98
+ </div>
99
+ );
100
+ };
101
+
102
+ export const Default: Story = {
103
+ args: {
104
+ step: 1,
105
+ title: "Default Step",
106
+ description: "This is a default step with no special state",
107
+ },
108
+ };
109
+
110
+ export const Active: Story = {
111
+ args: {
112
+ step: 2,
113
+ title: "Active Step",
114
+ description: "This step is currently active",
115
+ isActive: true,
116
+ icon: <Shield className="w-4 h-4" />,
117
+ },
118
+ };
119
+
120
+ export const Completed: Story = {
121
+ args: {
122
+ step: 3,
123
+ title: "Completed Step",
124
+ description: "This step has been completed",
125
+ isCompleted: true,
126
+ icon: <Link className="w-4 h-4" />,
127
+ },
128
+ };
129
+
130
+ export const WithChildSteps: Story = {
131
+ render: () => <StepWithDynamicChildren />,
132
+ };
@@ -0,0 +1,234 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { cn } from "@/lib/utils";
5
+ import { Check, Circle } from "lucide-react";
6
+ import { motion } from "framer-motion";
7
+ import { useRTL } from '@/contexts/RTLContext';
8
+
9
+ export interface ChildStepProps {
10
+ id: string;
11
+ title: string;
12
+ description?: string;
13
+ isActive?: boolean;
14
+ isCompleted?: boolean;
15
+ }
16
+
17
+ export interface StepProps {
18
+ step: number;
19
+ title: string;
20
+ description?: string;
21
+ icon?: React.ReactNode;
22
+ isActive?: boolean;
23
+ isCompleted?: boolean;
24
+ isLast?: boolean;
25
+ baseColor?: string;
26
+ onStepClick?: () => void;
27
+ height?: number;
28
+ isChildStep?: boolean;
29
+ isParentPending?: boolean;
30
+ notAuthenticated?: boolean;
31
+ isRTL?: boolean;
32
+ }
33
+
34
+ export interface StepContextType {
35
+ activeStep: number;
36
+ completedSteps: number[];
37
+ baseColor: string;
38
+ onStepComplete: (step: number) => void;
39
+ spacing?: number;
40
+ }
41
+
42
+ export const StepContext = React.createContext<StepContextType>({
43
+ activeStep: 1,
44
+ completedSteps: [],
45
+ baseColor: "#004d4d",
46
+ onStepComplete: () => { },
47
+ spacing: 24,
48
+ });
49
+
50
+ export const Step = React.forwardRef<HTMLDivElement, StepProps>(
51
+ (
52
+ {
53
+ title,
54
+ description,
55
+ icon,
56
+ isActive = false,
57
+ isCompleted = false,
58
+ isParentPending = false,
59
+ isLast = false,
60
+ baseColor = "#004d4d",
61
+ onStepClick,
62
+ height = 96,
63
+ isChildStep = false,
64
+ notAuthenticated = false,
65
+ isRTL = false,
66
+ },
67
+ ref
68
+ ) => {
69
+
70
+ const getStepIndicator = () => {
71
+ const sizeClasses = isChildStep ? "w-5 h-5" : "w-8 h-8";
72
+
73
+ if (isCompleted) {
74
+ return (
75
+ <motion.div
76
+ initial={{ scale: 0 }}
77
+ animate={{ scale: 1 }}
78
+ transition={{ duration: 0.3, ease: "easeOut" }}
79
+ className={cn(sizeClasses, `rounded-full bg-white flex items-center justify-center`, isChildStep ? "bg-transparent border-none" : '')}
80
+ >
81
+ {isChildStep ? <Circle className={cn("w-3 h-3", `fill-white mt-1`, isRTL && "mr-[12px]")} /> : <Check className={cn("w-4 h-4", `text-[${baseColor}]`)} />}
82
+ </motion.div >
83
+ );
84
+ }
85
+
86
+ if (isActive) {
87
+ return (
88
+ <motion.div
89
+ initial={{ scale: 0.8 }}
90
+ animate={{ scale: 1 }}
91
+ transition={{ duration: 0.3, ease: "easeOut" }}
92
+ className={cn(sizeClasses, "rounded-full bg-white flex items-center justify-center", isChildStep ? "bg-transparent border-none" : '')}
93
+ >
94
+ {icon && !isChildStep ? (
95
+ <div className={cn("w-4 h-4", `text-[${baseColor}]`)}>{icon}</div>
96
+ ) : (
97
+ <Circle color="white" className={cn(isChildStep ? "w-3 h-3 border-none" : "w-4 h-4", `fill-white mt-1`)} />
98
+ )}
99
+ </motion.div>
100
+ );
101
+ }
102
+
103
+ return (
104
+ <div className={cn(sizeClasses, "rounded-full border-1 border-white/50 flex items-center justify-center", isChildStep && "border-none", isParentPending && "bg-white")}>
105
+ {icon && !isChildStep ? (
106
+ <div className={cn(isChildStep ? "w-3 h-3" : "w-4 h-4", "text-white/50", isParentPending && `text-[${baseColor}]`)}>{icon}</div>
107
+ ) : (
108
+ <Circle className={cn(isChildStep ? "w-3 h-3" : "w-4 h-4", "text-white/50")} />
109
+ )}
110
+ </div>
111
+ );
112
+ };
113
+
114
+ return (
115
+ <div
116
+ ref={ref}
117
+ className={cn(
118
+ "relative flex flex-col gap-2 cursor-default transition-all duration-200",
119
+ isLast && "mb-0",
120
+ isChildStep && "ml-1.5"
121
+ )}
122
+ style={{ minHeight: `${height}px` }}
123
+ onClick={onStepClick}
124
+ >
125
+ <div className="flex items-start gap-4">
126
+ <div className="flex-shrink-0">{notAuthenticated ? <motion.div
127
+ initial={{ scale: 0.8 }}
128
+ animate={{ scale: 1 }}
129
+ transition={{ duration: 0.3, ease: "easeOut" }}
130
+ className={cn('w-8 h-8', "rounded-full bg-white flex items-center justify-center", isChildStep && "bg-transparent border-none")}
131
+ >
132
+ {icon && !isChildStep ? (
133
+ <div className={cn("w-4 h-4", `text-[${baseColor}]`)}>{icon}</div>
134
+ ) : (
135
+ <Circle className={cn(isChildStep ? "w-3 h-3 border-none" : "w-4 h-4", `fill-white mt-1`)} />
136
+ )}
137
+ </motion.div> : getStepIndicator()}</div>
138
+ <div className="flex-1">
139
+ <h3
140
+ className={cn(
141
+ "text-white/80 transition-all duration-200",
142
+ isActive && !isParentPending ? "text-lg" : "text-sm",
143
+ "font-light",
144
+ (isActive || isCompleted || isParentPending) && "font-medium text-white",
145
+ isActive ? "mt-0" : '',
146
+ isChildStep ? 'mb-2' : ' mt-1'
147
+ )}
148
+ >
149
+ {title}
150
+ </h3>
151
+ {description && (
152
+ <p className={cn(
153
+ "text-white/60 mt-1",
154
+ isChildStep ? "text-xs" : "text-sm"
155
+ )}>
156
+ {description}
157
+ </p>
158
+ )}
159
+ </div>
160
+ </div>
161
+ </div>
162
+ );
163
+ }
164
+ );
165
+
166
+ Step.displayName = "Step";
167
+
168
+ export interface StepConnectorProps {
169
+ isActive: boolean;
170
+ isCompleted: boolean;
171
+ baseColor?: string;
172
+ }
173
+
174
+ export const StepConnector = ({ isActive, isCompleted, baseColor = "#FFF" }: StepConnectorProps) => {
175
+ const { isRTL } = useRTL();
176
+
177
+ return (
178
+ <div className={`absolute ${isRTL ? 'right-4' : 'left-4'} top-6 bottom-0 w-[1px]`}>
179
+ <motion.div
180
+ initial={{ height: 0, opacity: 0 }}
181
+ animate={{
182
+ height: "100%",
183
+ opacity: 1,
184
+ background: isCompleted
185
+ ? baseColor
186
+ : isActive
187
+ ? "linear-gradient(to bottom, #fff 50%, transparent 50%)"
188
+ : "transparent",
189
+ backgroundSize: "4px 4px",
190
+ }}
191
+ transition={{
192
+ duration: 0.8,
193
+ ease: "easeInOut",
194
+ opacity: { duration: 0.3 }
195
+ }}
196
+ className="w-full"
197
+ />
198
+ </div>
199
+ );
200
+ };
201
+
202
+ export interface StepGroupProps {
203
+ children: React.ReactNode;
204
+ activeStep: number;
205
+ completedSteps: number[];
206
+ baseColor?: string;
207
+ onStepComplete?: (step: number) => void;
208
+ className?: string;
209
+ spacing?: number;
210
+ }
211
+
212
+ export const StepGroup = ({
213
+ children,
214
+ activeStep,
215
+ completedSteps,
216
+ baseColor = "#FFF",
217
+ onStepComplete = () => { },
218
+ className,
219
+ spacing = 24,
220
+ }: StepGroupProps) => {
221
+ const contextValue = {
222
+ activeStep,
223
+ completedSteps,
224
+ baseColor,
225
+ onStepComplete,
226
+ spacing,
227
+ };
228
+
229
+ return (
230
+ <StepContext.Provider value={contextValue}>
231
+ <div className={cn("relative", className)}>{children}</div>
232
+ </StepContext.Provider>
233
+ );
234
+ };
@@ -0,0 +1,136 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ Stepper,
9
+ StepperIndicator,
10
+ StepperItem,
11
+ StepperTrigger,
12
+ } from "@/components/ui/stepper";
13
+ import { motion } from "framer-motion";
14
+ import { useMediaQuery } from "@/hooks/use-media-query";
15
+
16
+ interface StepperProgressProps extends React.HTMLAttributes<HTMLDivElement> {
17
+ steps: number;
18
+ currentStep: number;
19
+ onStepChange?: (step: number) => void;
20
+ showControls?: boolean;
21
+ className?: string;
22
+ childProgress?: Record<number, number>; // Tracks progress for steps with children
23
+ maxChildProgress?: Record<number, number>; // Max progress for each parent
24
+ }
25
+
26
+ export function StepperProgress({
27
+ steps,
28
+ currentStep,
29
+ onStepChange,
30
+ showControls = false,
31
+ className,
32
+ childProgress = {},
33
+ maxChildProgress = {},
34
+ ...props
35
+ }: StepperProgressProps) {
36
+ const stepsArray = Array.from({ length: steps }, (_, i) => i + 1);
37
+ const isMobile = useMediaQuery("(max-width: 768px)");
38
+
39
+ // Initialize correctly on mobile screens for the login page
40
+ React.useEffect(() => {
41
+ if (isMobile && window.location.pathname === '/login') {
42
+ // Reset child progress so progress bar doesn't show as completed
43
+ if (Object.keys(childProgress).length === 0 && currentStep === 1) {
44
+ // Skip this setup if the childProgress is already set
45
+ onStepChange?.(1);
46
+ }
47
+ }
48
+ }, [isMobile, currentStep, onStepChange, childProgress]);
49
+
50
+ const handlePrev = () => {
51
+ if (currentStep > 1 && onStepChange) {
52
+ onStepChange(currentStep - 1);
53
+ }
54
+ };
55
+
56
+ const handleNext = () => {
57
+ if (currentStep < steps && onStepChange) {
58
+ onStepChange(currentStep + 1);
59
+ }
60
+ };
61
+
62
+ return (
63
+ <div
64
+ className={cn("flex items-center gap-2 w-full", className)}
65
+ {...props}
66
+ >
67
+ {showControls && (
68
+ <Button
69
+ className="shrink-0"
70
+ variant="ghost"
71
+ size="icon"
72
+ onClick={handlePrev}
73
+ disabled={currentStep === 1}
74
+ aria-label="Previous step"
75
+ >
76
+ <ChevronLeftIcon size={16} strokeWidth={2} aria-hidden="true" />
77
+ </Button>
78
+ )}
79
+
80
+ <Stepper value={currentStep} onValueChange={onStepChange} className="gap-1 w-full">
81
+ {stepsArray.map((step) => {
82
+ const isCompleted = step < currentStep;
83
+ const hasPartialProgress = childProgress[step] !== undefined;
84
+ const progressPercentage = hasPartialProgress ?
85
+ (childProgress[step] / (maxChildProgress[step] || 1)) * 100 : 0;
86
+
87
+ return (
88
+ <StepperItem
89
+ key={step}
90
+ step={step}
91
+ className="flex-1 relative"
92
+ completed={isCompleted}
93
+ >
94
+ <StepperTrigger className="w-full flex-col items-start gap-2" asChild>
95
+ <div className="relative w-full h-1 bg-gray-200 rounded-full overflow-hidden">
96
+ {/* Partial progress indicator */}
97
+ {hasPartialProgress && !isCompleted && (
98
+ <motion.div
99
+ className="absolute left-0 top-0 h-full bg-primary"
100
+ initial={{ width: 0 }}
101
+ animate={{ width: `${progressPercentage}%` }}
102
+ transition={{ duration: 0.5, ease: "easeInOut" }}
103
+ />
104
+ )}
105
+
106
+ {/* Full progress indicator - visible when step is completed */}
107
+ {isCompleted && (
108
+ <div className="absolute left-0 top-0 h-full w-full bg-primary" />
109
+ )}
110
+
111
+ {/* Hidden indicator for screen readers */}
112
+ <StepperIndicator asChild className="opacity-0 pointer-events-none">
113
+ <span className="sr-only">{step}</span>
114
+ </StepperIndicator>
115
+ </div>
116
+ </StepperTrigger>
117
+ </StepperItem>
118
+ );
119
+ })}
120
+ </Stepper>
121
+
122
+ {showControls && (
123
+ <Button
124
+ className="shrink-0"
125
+ variant="ghost"
126
+ size="icon"
127
+ onClick={handleNext}
128
+ disabled={currentStep === steps}
129
+ aria-label="Next step"
130
+ >
131
+ <ChevronRightIcon size={16} strokeWidth={2} aria-hidden="true" />
132
+ </Button>
133
+ )}
134
+ </div>
135
+ );
136
+ }