@truworth/twc-auth 1.2.11 → 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.
@@ -37,8 +37,8 @@ const AdvancedTransitionWrapper = ({ children, isVisible = true, direction = 'ri
37
37
  export { AdvancedTransitionWrapper };
38
38
  const getAdvancedVariants = (direction, type, duration, delay, options) => {
39
39
  // Extract options with defaults
40
- const { bounce = 0.25, damping = 10, mass = 1, velocity = 0, depth = 100, perspective = 1000, intensity = 1 } = options;
41
- // Base variants
40
+ const { bounce = 0.25, damping = 10, mass = 1, velocity = 0, depth = 100, intensity = 1 } = options;
41
+ // Base variants - using Record type to allow dynamic property assignment
42
42
  const baseVariants = {
43
43
  initial: { opacity: 0 },
44
44
  enter: {
@@ -104,17 +104,14 @@ const getAdvancedVariants = (direction, type, duration, delay, options) => {
104
104
  baseVariants.initial = {
105
105
  ...baseVariants.initial,
106
106
  [flipAxis]: direction === 'up' || direction === 'left' ? flipAmount : -flipAmount,
107
- transformPerspective: perspective
108
107
  };
109
108
  baseVariants.enter = {
110
109
  ...baseVariants.enter,
111
110
  [flipAxis]: 0,
112
- transformPerspective: perspective
113
111
  };
114
112
  baseVariants.exit = {
115
113
  ...baseVariants.exit,
116
114
  [flipAxis]: direction === 'up' || direction === 'left' ? -flipAmount : flipAmount,
117
- transformPerspective: perspective
118
115
  };
119
116
  break;
120
117
  }
@@ -281,21 +278,18 @@ const getAdvancedVariants = (direction, type, duration, delay, options) => {
281
278
  scale: direction === 'up' || direction === 'left' ? 1.5 * intensity : 0.6 / Math.max(0.1, intensity),
282
279
  opacity: 0,
283
280
  z: direction === 'up' || direction === 'left' ? -depth : depth,
284
- transformPerspective: perspective
285
281
  };
286
282
  baseVariants.enter = {
287
283
  ...baseVariants.enter,
288
284
  scale: 1,
289
285
  opacity: 1,
290
286
  z: 0,
291
- transformPerspective: perspective
292
287
  };
293
288
  baseVariants.exit = {
294
289
  ...baseVariants.exit,
295
290
  scale: direction === 'up' || direction === 'left' ? 0.6 / Math.max(0.1, intensity) : 1.5 * intensity,
296
291
  opacity: 0,
297
292
  z: direction === 'up' || direction === 'left' ? depth : -depth,
298
- transformPerspective: perspective
299
293
  };
300
294
  break;
301
295
  }
@@ -34,7 +34,7 @@ const AuthPackageContext = React.createContext(null);
34
34
  * @param openChatSupport - Optional callback to open chat support
35
35
  * @param socialLoginConfig - Optional social login configuration
36
36
  */
37
- const AuthProvider = ({ children, LogoComponent, session, appConfig, onLogin, onLogout, onLaunchAuthSession, onRefreshSession, openChatSupport, socialLoginConfig }) => {
37
+ const AuthProvider = ({ children, router, basePath = '', LogoComponent, session, appConfig, onLogin, onLogout, onLaunchAuthSession, onRefreshSession, openChatSupport, socialLoginConfig }) => {
38
38
  const [isLoadingProfile, setIsLoadingProfile] = useState(false);
39
39
  const [profile, setProfile] = useState(null);
40
40
  const [client, setClient] = useState(null);
@@ -168,6 +168,8 @@ const AuthProvider = ({ children, LogoComponent, session, appConfig, onLogin, on
168
168
  };
169
169
  const AuthPackageContextValues = {
170
170
  token,
171
+ router,
172
+ basePath,
171
173
  LogoComponent,
172
174
  appConfig: appConfigValue,
173
175
  registrationMethod,
@@ -0,0 +1,83 @@
1
+ import { useContext, useMemo } from 'react';
2
+ import { AuthPackageContext } from '../contexts/AuthContext';
3
+ /**
4
+ * useNavigator Hook
5
+ *
6
+ * Provides framework-agnostic navigation capabilities.
7
+ * Uses the RouterAdapter provided by the host app through AuthProvider.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * const navigator = useNavigator();
12
+ *
13
+ * // Relative navigation (uses basePath)
14
+ * navigator.push('/registration');
15
+ *
16
+ * // Absolute navigation (ignores basePath)
17
+ * navigator.pushAbsolute('/dashboard');
18
+ *
19
+ * // With query params
20
+ * navigator.push('/registration', { query: { email: 'test@example.com' } });
21
+ *
22
+ * // Access query params
23
+ * const { email } = navigator.query;
24
+ * ```
25
+ */
26
+ const useNavigator = () => {
27
+ const context = useContext(AuthPackageContext);
28
+ if (!context?.router) {
29
+ throw new Error('useNavigator: router not found in context. ' +
30
+ 'Make sure to wrap your component with AuthProvider and pass a router adapter.');
31
+ }
32
+ const { router, basePath = '' } = context;
33
+ const navigator = useMemo(() => ({
34
+ /**
35
+ * Navigate to a path relative to basePath
36
+ */
37
+ push: (path, options) => {
38
+ const fullPath = path.startsWith('/') ? `${basePath}${path}` : `${basePath}/${path}`;
39
+ router.push(fullPath, options);
40
+ },
41
+ /**
42
+ * Replace current path with a path relative to basePath
43
+ */
44
+ replace: (path, options) => {
45
+ const fullPath = path.startsWith('/') ? `${basePath}${path}` : `${basePath}/${path}`;
46
+ router.replace(fullPath, options);
47
+ },
48
+ /**
49
+ * Navigate to an absolute path (ignores basePath)
50
+ */
51
+ pushAbsolute: (path, options) => {
52
+ router.push(path, options);
53
+ },
54
+ /**
55
+ * Replace with an absolute path (ignores basePath)
56
+ */
57
+ replaceAbsolute: (path, options) => {
58
+ router.replace(path, options);
59
+ },
60
+ /**
61
+ * Go back to the previous page
62
+ */
63
+ back: () => router.back(),
64
+ /**
65
+ * Get current query parameters
66
+ */
67
+ get query() {
68
+ return router.getQuery();
69
+ },
70
+ /**
71
+ * Get current pathname
72
+ */
73
+ get pathname() {
74
+ return router.getPathname();
75
+ },
76
+ /**
77
+ * The base path where the auth module is mounted
78
+ */
79
+ basePath,
80
+ }), [router, basePath]);
81
+ return navigator;
82
+ };
83
+ export { useNavigator };
@@ -5,6 +5,7 @@ export * from './contexts/type';
5
5
  // export hooks
6
6
  export * from './hooks/useAuthContext';
7
7
  export * from './hooks/useRequest';
8
+ export * from './hooks/useNavigator';
8
9
  // export axiosClient
9
10
  export * from './api/axiosClient';
10
11
  // export navigator/router
@@ -1,5 +1,4 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import Image from 'next/image';
3
2
  import React, { useMemo } from "react";
4
3
  import { Typography, SelectField, Flex } from '@truworth/twc-web-design';
5
4
  import { useCountryCode } from '../../hooks/internal/useCountryCode';
@@ -22,10 +21,10 @@ export const CountryCodeDropdown = React.memo(({ selectedCountry, handleSelect }
22
21
  const code = item.countryCode.toLowerCase();
23
22
  const flagUrl = `https://cdn.thewellnesscorner.com/country-flags/${code}.png`;
24
23
  return {
25
- leftIcon: (_jsx(_Fragment, { children: isValidCountryCode(code) && (_jsx(Image, { src: flagUrl, width: 20, height: 20, className: "h-auto shrink-0", alt: `${item.name} flag`, loading: "lazy" })) })),
24
+ leftIcon: (_jsx(_Fragment, { children: isValidCountryCode(code) && (_jsx("img", { src: flagUrl, width: 20, height: 20, className: "h-5 w-5 shrink-0", alt: `${item.name} flag`, loading: "lazy" })) })),
26
25
  label: (_jsxs(Typography, { type: "body", size: "small", children: [item.name, " (+", item.phoneCode, ")"] })),
27
26
  value: code,
28
- selectedField: (_jsxs(Flex, { align: "center", className: "gap-2 pl-2 pr-2 cursor-pointer", children: [isValidCountryCode(code) && (_jsx(Image, { src: flagUrl, width: 20, height: 20, className: "h-auto shrink-0", alt: `${item.name} flag`, loading: "lazy" })), _jsx(Typography, { type: "body", size: "medium", className: "countryCode cursor-pointer", children: `+${item.phoneCode}` })] })),
27
+ selectedField: (_jsxs(Flex, { align: "center", className: "gap-2 pl-2 pr-2 cursor-pointer", children: [isValidCountryCode(code) && (_jsx("img", { src: flagUrl, width: 20, height: 20, className: "h-5 w-5 shrink-0", alt: `${item.name} flag`, loading: "lazy" })), _jsx(Typography, { type: "body", size: "medium", className: "countryCode cursor-pointer", children: `+${item.phoneCode}` })] })),
29
28
  };
30
29
  });
31
30
  }, [countries]);
@@ -3,7 +3,7 @@ import { Flex, Form, ResponsiveModal, TextInput, Typography, useForm } from '@tr
3
3
  import { ScreenLayout } from "../../components/ScreenLayout";
4
4
  import { useEnterMobile } from "./hooks/internal/useEnterMobile";
5
5
  import { CountryCodeDropdown } from '../CountryCode/components/CountryCodeDropdown';
6
- import { useRouter } from 'next/router';
6
+ import { useNavigator } from '../../hooks/useNavigator';
7
7
  import { useState } from 'react';
8
8
  import { ExistingAccountsSheet } from './components/ExistingAccountsSheet';
9
9
  import LoginWithMobileOTP from '../LoginWithMobileOTP';
@@ -22,7 +22,7 @@ const EnterMobile = ({ onPressBack }) => {
22
22
  name: "INDIA",
23
23
  });
24
24
  const form = useForm();
25
- const router = useRouter();
25
+ const navigator = useNavigator();
26
26
  const { loading, phone, isValidPhone, countryCode, setCountryCode, existingAccounts, showExistingAccountsSheet, setShowExistingAccountsSheet, handleValidatePhone, handleEnterMobile, } = useEnterMobile();
27
27
  const handleSubmit = () => {
28
28
  handleValidatePhone({
@@ -32,8 +32,7 @@ const EnterMobile = ({ onPressBack }) => {
32
32
  setShowLoginWithMobileOTPModal(true);
33
33
  }
34
34
  else {
35
- router.push({
36
- pathname: '/registration',
35
+ navigator.pushAbsolute('/registration', {
37
36
  query: { phone, registrationMethod: RegistrationMethod.MOBILE }
38
37
  });
39
38
  }
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useState } from 'react';
3
3
  import { Flex } from '@truworth/twc-web-design';
4
- import { useRouter } from 'next/router';
5
4
  import { AdvancedTransitionWrapper } from '../../../../components/AdvancedTransitionWrapper';
6
5
  import { useAuthPackageContext } from '../../../../hooks/internal/useAuthPackageContext';
6
+ import { useNavigator } from '../../../../hooks/useNavigator';
7
7
  import EnterEmail from '../../../EnterEmail/index';
8
8
  import EnterPassword from '../../../EnterPassword/index';
9
9
  import ResetPassword from '../../../ResetPassword/index';
@@ -16,14 +16,13 @@ const LoginWebComponent = () => {
16
16
  const [sessionToken, setSessionToken] = useState('');
17
17
  const [selectedClient, setSelectedClient] = useState(null);
18
18
  const { LogoComponent } = useAuthPackageContext();
19
- const router = useRouter();
19
+ const navigator = useNavigator();
20
20
  const onRedirectToPassword = (email) => {
21
21
  setEmail(email);
22
22
  setLoginState('EnterPassword');
23
23
  };
24
24
  const onRedirectToSignUp = (email) => {
25
- router.push({
26
- pathname: '/registration',
25
+ navigator.pushAbsolute('/registration', {
27
26
  query: { email, registrationMethod: 'email' }
28
27
  });
29
28
  };
@@ -1,59 +1,58 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { Suspense, lazy, useEffect, useState } from 'react';
2
3
  import { Flex, Typography } from '@truworth/twc-web-design';
3
4
  import { useSSOCallback } from '../../hooks/internal/useSSOCallback';
4
- import { useRouter } from 'next/router';
5
- import { useEffect, useState } from 'react';
5
+ import { useNavigator } from '../../../../../hooks/useNavigator';
6
6
  import redirectAnimation from '../../../../../../assets/animation/redirect-home.json';
7
7
  import { RegistrationMethod } from '../../../../../enums';
8
- import dynamic from 'next/dynamic';
9
- const Lottie = dynamic(() => import('react-lottie'), { ssr: false });
8
+ const Lottie = lazy(() => import('react-lottie'));
10
9
  const SSOCallbackComponents = () => {
11
- const router = useRouter();
10
+ const navigator = useNavigator();
12
11
  const [clientId, setClientId] = useState('');
13
12
  const [code, setCode] = useState('');
14
13
  // Resolve code/token from query (preferred) or localStorage; clientId/authMethod from localStorage only
15
14
  useEffect(() => {
16
- const { code } = router.query;
15
+ const query = navigator.query;
16
+ const codeFromQuery = query.code;
17
17
  if (typeof window !== 'undefined') {
18
18
  try {
19
19
  const storedClientId = localStorage.getItem('clientId') || '';
20
20
  setClientId(storedClientId);
21
- setCode(typeof code === 'string' ? code : '');
21
+ setCode(typeof codeFromQuery === 'string' ? codeFromQuery : '');
22
22
  }
23
23
  catch (error) {
24
24
  console.log('Failed to read from localStorage:', error);
25
- setCode(typeof code === 'string' ? code : '');
25
+ setCode(typeof codeFromQuery === 'string' ? codeFromQuery : '');
26
26
  // Fallback: proceed without stored values
27
27
  }
28
28
  }
29
- }, [router.query]);
29
+ }, [navigator.query]);
30
30
  const { result, error } = useSSOCallback({ clientId, code });
31
31
  useEffect(() => {
32
32
  if (result?.registrationToken) {
33
- router.push({
34
- pathname: '/registration',
33
+ navigator.pushAbsolute('/registration', {
35
34
  query: {
36
35
  registrationMethod: RegistrationMethod.SSO,
37
36
  ...result,
38
37
  }
39
38
  });
40
39
  }
41
- }, [result, router]);
40
+ }, [result, navigator]);
42
41
  useEffect(() => {
43
42
  if (error) {
44
43
  setTimeout(() => {
45
- router.back();
44
+ navigator.back();
46
45
  }, 1500);
47
46
  }
48
- }, [error, router]);
49
- return (_jsx(Flex, { justify: 'center', align: 'center', className: 'h-[100vh]', children: _jsxs("div", { children: [_jsx(Lottie
50
- //@ts-ignore
51
- , {
47
+ }, [error, navigator]);
48
+ return (_jsx(Flex, { justify: 'center', align: 'center', className: 'h-[100vh]', children: _jsxs("div", { children: [_jsx(Suspense, { fallback: _jsx("div", { className: "h-[230px] w-[250px]" }), children: _jsx(Lottie
52
49
  //@ts-ignore
53
- options: {
54
- animationData: redirectAnimation,
55
- loop: true,
56
- autoplay: true
57
- }, height: 230, width: 250 }), _jsx(Typography, { size: 'h6', type: 'heading', color: 'gray-400', children: "Authenticating and Redirecting ..." })] }) }));
50
+ , {
51
+ //@ts-ignore
52
+ options: {
53
+ animationData: redirectAnimation,
54
+ loop: true,
55
+ autoplay: true
56
+ }, height: 230, width: 250 }) }), _jsx(Typography, { size: 'h6', type: 'heading', color: 'gray-400', children: "Authenticating and Redirecting ..." })] }) }));
58
57
  };
59
58
  export { SSOCallbackComponents };
@@ -7,7 +7,7 @@ import { SupportDetails } from "../../../../components/SupportDetails";
7
7
  import { ScreenLayout } from "../../../../components/ScreenLayout";
8
8
  import { CountryCodeDropdown } from "../../../CountryCode/components/CountryCodeDropdown";
9
9
  import { useSignUp } from "../../hooks/internal/useSignUp";
10
- import { useRouter } from "next/router";
10
+ import { useNavigator } from "../../../../hooks/useNavigator";
11
11
  import ReCAPTCHA from 'react-google-recaptcha';
12
12
  import moment from "moment";
13
13
  import dayjs from 'dayjs';
@@ -23,12 +23,13 @@ const SignUpFormComponent = ({ userDetails, onContinue }) => {
23
23
  const [isHuman, setIsHuman] = useState(isProduction ? false : true);
24
24
  const [termsAndConditions, setTermsAndConditions] = useState(false);
25
25
  const [selectedCountry, setSelectedCountry] = useState({ countryCode: "in", phoneCode: "91", name: "INDIA" });
26
- const router = useRouter();
26
+ const navigator = useNavigator();
27
+ const routerQuery = navigator.query;
27
28
  const form = useForm({
28
29
  liveValidation: true,
29
30
  defaultValues: {
30
- firstName: userDetails.firstName ?? getQueryParam(router.query.firstName),
31
- lastName: userDetails.lastName ?? getQueryParam(router.query.lastName),
31
+ firstName: userDetails.firstName ?? getQueryParam(routerQuery.firstName),
32
+ lastName: userDetails.lastName ?? getQueryParam(routerQuery.lastName),
32
33
  dateOfBirth: userDetails.selectedDOB,
33
34
  email: userDetails.email ?? "",
34
35
  phone: userDetails.phone ?? "",
@@ -38,7 +39,7 @@ const SignUpFormComponent = ({ userDetails, onContinue }) => {
38
39
  });
39
40
  const values = form.watch();
40
41
  useEffect(() => {
41
- const rawMethod = router.query.registrationMethod;
42
+ const rawMethod = routerQuery.registrationMethod;
42
43
  const registrationMethodFromQuery = Array.isArray(rawMethod) ? rawMethod[0] : rawMethod;
43
44
  if (typeof registrationMethodFromQuery === 'string') {
44
45
  const method = registrationMethodFromQuery.toLowerCase();
@@ -50,11 +51,11 @@ const SignUpFormComponent = ({ userDetails, onContinue }) => {
50
51
  }
51
52
  onRegistrationMethodChange(method);
52
53
  }
53
- }, [router.query.registrationMethod]);
54
+ }, [routerQuery.registrationMethod]);
54
55
  const { loading, setGender, setSelectedDOB, phone, setPhone, email, setEmail, setReferralCode, setCountryCode, countryCode, linkedAccounts, registrationMethod, onRegistrationMethodChange, linkedAccountsSheet, showLinkedAccountsSheet, handleFirstNameChange, handleLastNameChange, handleEmailChange, handleMobileChange, handleSubmit, getLoginTypeText, appName, termsAndConditionsUrl } = useSignUp();
55
56
  useEffect(() => {
56
57
  if (!registrationMethod) {
57
- const registrationMethodFromQuery = router.query.registrationMethod;
58
+ const registrationMethodFromQuery = routerQuery.registrationMethod;
58
59
  if (typeof registrationMethodFromQuery === 'string') {
59
60
  const method = registrationMethodFromQuery.toLowerCase();
60
61
  onRegistrationMethodChange(method);
@@ -63,10 +64,10 @@ const SignUpFormComponent = ({ userDetails, onContinue }) => {
63
64
  ;
64
65
  }, [registrationMethod]);
65
66
  useEffect(() => {
66
- if (router.query.registrationToken) {
67
+ if (routerQuery.registrationToken) {
67
68
  form.reset({
68
- firstName: getQueryParam(router.query.firstName) ?? userDetails.firstName,
69
- lastName: getQueryParam(router.query.lastName) ?? userDetails.lastName,
69
+ firstName: getQueryParam(routerQuery.firstName) ?? userDetails.firstName,
70
+ lastName: getQueryParam(routerQuery.lastName) ?? userDetails.lastName,
70
71
  });
71
72
  }
72
73
  }, []);
@@ -93,7 +94,7 @@ const SignUpFormComponent = ({ userDetails, onContinue }) => {
93
94
  setIsHuman(Boolean(value));
94
95
  };
95
96
  const onGoToLogin = () => {
96
- router.push('/login');
97
+ navigator.pushAbsolute('/login');
97
98
  };
98
99
  const onProceed = () => {
99
100
  const params = {
@@ -102,8 +103,8 @@ const SignUpFormComponent = ({ userDetails, onContinue }) => {
102
103
  gender: values.gender,
103
104
  selectedDOB: values.dateOfBirth,
104
105
  referralCode: values.referralCode,
105
- email: email.length > 0 ? email : getQueryParam(router.query.email) || '',
106
- phone: phone.length > 0 ? phone : getQueryParam(router.query.phone) || '',
106
+ email: email.length > 0 ? email : getQueryParam(routerQuery.email) || '',
107
+ phone: phone.length > 0 ? phone : getQueryParam(routerQuery.phone) || '',
107
108
  countryCode,
108
109
  };
109
110
  onContinue(params);
@@ -197,12 +198,11 @@ const SignUpFormComponent = ({ userDetails, onContinue }) => {
197
198
  : _jsx(Typography, { type: "utility", size: "small", color: "red-600", children: "reCAPTCHA misconfigured. Set NEXT_PUBLIC_RECAPTCHA_SITE_KEY." })), _jsx(Row, { className: "py-0 my-5", children: _jsx(Col, { children: _jsx(CustomCheckbox, { checked: termsAndConditions, onClick: (e) => {
198
199
  setTermsAndConditions(prev => !prev);
199
200
  }, className: "mt-4", label: _jsxs(Typography, { type: "body", size: "medium", children: ["I accept the", _jsx("a", { target: "_blank", rel: "noopener noreferrer", href: termsAndConditionsUrl, className: "px-1 text-primary", children: "Terms & Conditions" }), "listed on ", appName] }) }) }) })] }) }), _jsxs(Flex, { align: 'center', className: 'p-0 mt-4', children: [_jsx(Typography, { type: "body", size: "large", className: "text-gray-700", children: "Already have an account?" }), _jsx(Button, { label: "Sign-In", variant: "link", size: "medium", className: "ml-2 h-auto px-0", onClick: onGoToLogin })] }), linkedAccountsSheet &&
200
- _jsx(LinkedAccountsSheet, { phone: phone, countryCode: countryCode, visible: linkedAccountsSheet, hide: () => showLinkedAccountsSheet(false), linkedAccounts: linkedAccounts, getLoginTypeText: getLoginTypeText })] }));
201
+ _jsx(LinkedAccountsSheet, { phone: phone, countryCode: countryCode, visible: linkedAccountsSheet, hide: () => showLinkedAccountsSheet(false), linkedAccounts: linkedAccounts, getLoginTypeText: getLoginTypeText, onGoToLogin: onGoToLogin })] }));
201
202
  };
202
- const LinkedAccountsSheet = ({ visible, hide, linkedAccounts, countryCode, phone, getLoginTypeText }) => {
203
- const router = useRouter();
203
+ const LinkedAccountsSheet = ({ visible, hide, linkedAccounts, countryCode, phone, getLoginTypeText, onGoToLogin }) => {
204
204
  return (_jsxs(ResponsiveModal, { open: visible, onClose: hide, onOpenChange: hide, title: _jsxs(_Fragment, { children: [_jsx(Typography, { type: "heading", size: "h6", children: "Mobile Number Already Linked" }), _jsxs(Typography, { type: "utility", size: "medium", color: "gray-600", className: "mt-2 mb-3", children: ["The mobile number +", countryCode, " ", String(phone).replace(/\d(?=\d{2})/g, 'X'), " is linked to the following ", linkedAccounts.length === 1 ? 'account' : `${linkedAccounts.length} accounts`, "."] }), _jsx("hr", {})] }), centered: true, showCloseButton: false, className: "px-0", footer: _jsxs(Flex, { direction: "column", children: [_jsx(Button, { isFullWidth: true, variant: "primary", label: "Go to Login", onClick: () => {
205
- router.push('/login');
205
+ onGoToLogin();
206
206
  hide();
207
207
  }, className: "!ml-0 mb-[-4px]" }), _jsx(SupportDetails, {})] }), children: [_jsx(Typography, { type: "utility", size: "medium", children: "Please login through any of the below account or contact our support team for assistance" }), linkedAccounts.map((item) => {
208
208
  const key = item.memberId ?? item.email ?? `${item.loginType}-${item.createdOn}`;
@@ -1,22 +1,23 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Flex } from "@truworth/twc-web-design";
3
- import { useRouter } from "next/router";
4
3
  import { useEffect, useState } from "react";
5
4
  import { AdvancedTransitionWrapper } from "../../../../components/AdvancedTransitionWrapper";
6
5
  import { SignUpFormComponent } from "../SignUpForm";
7
6
  import CreatePassword from "../../../CreatePassword";
8
7
  import UserConsent from "../../../UserConsent";
8
+ import { useNavigator } from "../../../../hooks/useNavigator";
9
9
  const SignUpWebComponent = () => {
10
- const router = useRouter();
10
+ const navigator = useNavigator();
11
11
  const [signUpStep, setSignUpStep] = useState('registration');
12
12
  const [userDetails, setUserDetails] = useState({});
13
13
  const [registrationToken, setRegistrationToken] = useState('');
14
14
  const [fbUserId, setFbUserId] = useState('');
15
15
  const [googleUserId, setGoogleUserId] = useState('');
16
16
  useEffect(() => {
17
- const rawRegistrationToken = router.query.registrationToken;
18
- const rawFbUserId = router.query.fbUserId;
19
- const rawGoogleUserId = router.query.googleUserId;
17
+ const query = navigator.query;
18
+ const rawRegistrationToken = query.registrationToken;
19
+ const rawFbUserId = query.fbUserId;
20
+ const rawGoogleUserId = query.googleUserId;
20
21
  if (typeof rawRegistrationToken === 'string') {
21
22
  setRegistrationToken(rawRegistrationToken);
22
23
  }
@@ -26,7 +27,7 @@ const SignUpWebComponent = () => {
26
27
  if (typeof rawGoogleUserId === 'string') {
27
28
  setGoogleUserId(rawGoogleUserId);
28
29
  }
29
- }, [router.query.registrationToken]);
30
+ }, [navigator.query.registrationToken]);
30
31
  const renderStep = () => {
31
32
  switch (signUpStep) {
32
33
  case 'registration':
@@ -1,19 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import dynamic from 'next/dynamic';
3
- import React from 'react';
4
- import Image from 'next/image';
2
+ import React, { Suspense, lazy } from 'react';
5
3
  import { useEffect, useState } from "react";
6
4
  import { Info } from 'lucide-react';
7
- import { useRouter } from "next/router";
8
5
  import { Flex, Typography, ResponsiveModal as Modal, Button, ResponsiveModal } from '@truworth/twc-web-design';
9
6
  import { useAuthPackageContext } from '../../hooks/internal/useAuthPackageContext';
10
7
  import { CDN_IMAGES_URL } from '../../constants/cdn-url';
11
8
  import { useConsent } from './hooks/internal/useConsent';
12
9
  import { ScreenLayout } from "../../components/ScreenLayout";
10
+ import { useNavigator } from '../../hooks/useNavigator';
13
11
  import VerifyMobile from "../VerifyMobile";
14
12
  import VerifyEmail from "../VerifyEmail";
15
13
  import redirectHome from '../../../assets/animation/redirect-home.json';
16
- const Lottie = dynamic(() => import('react-lottie'), { ssr: false });
14
+ const Lottie = lazy(() => import('react-lottie'));
17
15
  const UserConsent = ({ userDetails, handleBack }) => {
18
16
  const { email, phone } = userDetails || {};
19
17
  const [redirectModal, setRedirectModal] = useState(false);
@@ -22,6 +20,7 @@ const UserConsent = ({ userDetails, handleBack }) => {
22
20
  const [sessionToken, setSessionToken] = useState('');
23
21
  const { onLogin, appConfig: { appName, privacyPolicyUrl }, LogoComponent, onTokenChange } = useAuthPackageContext();
24
22
  const { loading, onAgree } = useConsent();
23
+ const navigator = useNavigator();
25
24
  const onAgreeHandler = (res) => {
26
25
  const { token, member, emailVerificationRequired, mobileVerificationRequired, sessionToken } = res;
27
26
  if (token && member) {
@@ -54,32 +53,31 @@ const UserConsent = ({ userDetails, handleBack }) => {
54
53
  : typeof LogoComponent === 'function'
55
54
  ? React.createElement(LogoComponent)
56
55
  :
57
- _jsx(Image, { src: `${CDN_IMAGES_URL ?? ''}/new-twc_logo.svg`, alt: "logo", priority: true, width: 176, height: 40, className: "mb-9 h-auto" }), _jsx(Flex, { className: 'p-0 lg:gap-8 flex-col-reverse lg:flex-row', children: _jsxs(Flex, { direction: 'column', className: 'p-0', children: [_jsx(Typography, { type: 'heading', size: 'h5', className: 'mb-3 text-gray-900', children: "Thank you for your trust & sharing your personal information with us." }), _jsx(Typography, { type: 'heading', size: 'h6', className: "mb-6 text-gray-600", children: "User Consent" }), _jsxs(Typography, { type: 'body', size: 'medium', className: 'lg:text-sm text-gray-800', children: ["I provide ", appName, " with the consent to collect, use, store, share, and/or otherwise process my personal information including but not limited to Name, Gender (biological sex at the time of birth), Date of Birth and Email Address, Mobile Number, and other health vital & lifestyle information). The information is collected for the specified, explicit and legitimate purposes only - which is to assess me or my family's Health Risk, schedule Investigations, Health Check-ups, Doctor Consultations, Procedure, Treatment, provide Health & Wellness Coaching and Payments and ensure that the same is not excessive in relation to the purposes for which it is being collected, and also ensure that such personal information is retained only as long as the user is registered on the platform. I, also agree to receive Email / SMS/WhatsApp alerts and calls in connection with me or my family's health & wellness services."] }), _jsxs(Typography, { type: 'body', size: 'medium', className: "lg:text-sm mt-5 text-gray-800", children: ["Please refer to the 'Terms & Conditions', Privacy & Data Protection Policy listed on ", _jsxs("a", { href: privacyPolicyUrl, target: '_blank', rel: "noopener noreferrer", className: "text-primary block font-semibold", children: [appName, "'s Privacy & Data Protection Policy"] })] }), _jsxs(Flex, { className: '!px-0 flex-col lg:flex-row mt-8', children: [_jsx(Button, { label: "Yes, I Agree", variant: "primary", loading: loading, onClick: () => onAgree({
56
+ _jsx("img", { src: `${CDN_IMAGES_URL ?? ''}/new-twc_logo.svg`, alt: "logo", width: 176, height: 40, className: "mb-9 h-auto w-[176px]" }), _jsx(Flex, { className: 'p-0 lg:gap-8 flex-col-reverse lg:flex-row', children: _jsxs(Flex, { direction: 'column', className: 'p-0', children: [_jsx(Typography, { type: 'heading', size: 'h5', className: 'mb-3 text-gray-900', children: "Thank you for your trust & sharing your personal information with us." }), _jsx(Typography, { type: 'heading', size: 'h6', className: "mb-6 text-gray-600", children: "User Consent" }), _jsxs(Typography, { type: 'body', size: 'medium', className: 'lg:text-sm text-gray-800', children: ["I provide ", appName, " with the consent to collect, use, store, share, and/or otherwise process my personal information including but not limited to Name, Gender (biological sex at the time of birth), Date of Birth and Email Address, Mobile Number, and other health vital & lifestyle information). The information is collected for the specified, explicit and legitimate purposes only - which is to assess me or my family's Health Risk, schedule Investigations, Health Check-ups, Doctor Consultations, Procedure, Treatment, provide Health & Wellness Coaching and Payments and ensure that the same is not excessive in relation to the purposes for which it is being collected, and also ensure that such personal information is retained only as long as the user is registered on the platform. I, also agree to receive Email / SMS/WhatsApp alerts and calls in connection with me or my family's health & wellness services."] }), _jsxs(Typography, { type: 'body', size: 'medium', className: "lg:text-sm mt-5 text-gray-800", children: ["Please refer to the 'Terms & Conditions', Privacy & Data Protection Policy listed on ", _jsxs("a", { href: privacyPolicyUrl, target: '_blank', rel: "noopener noreferrer", className: "text-primary block font-semibold", children: [appName, "'s Privacy & Data Protection Policy"] })] }), _jsxs(Flex, { className: '!px-0 flex-col lg:flex-row mt-8', children: [_jsx(Button, { label: "Yes, I Agree", variant: "primary", loading: loading, onClick: () => onAgree({
58
57
  userDetails,
59
58
  source: 'web',
60
59
  onResult: onAgreeHandler
61
60
  }), className: "w-full lg:!w-auto lg:!mt-0" }), _jsx(Button, { label: "Not Now, I Will Register Later", variant: "link", onClick: () => setRedirectModal(true), className: "w-full lg:!w-auto lg:!mt-0" })] })] }) })] }), subTitle: "", onPressBack: handleBack }), redirectModal &&
62
- _jsx(RedirectToHomeModal, { redirectModal: redirectModal }), showVerifyMobileModal &&
61
+ _jsx(RedirectToHomeModal, { redirectModal: redirectModal, navigator: navigator }), showVerifyMobileModal &&
63
62
  _jsx(VerifyMobileModal, { phone: phone, visible: showVerifyMobileModal, hide: () => setShowVerifyMobileModal(false), sessionToken: sessionToken }), showVerifyEmailModal &&
64
63
  _jsx(VerifyEmailModal, { email: email, visible: showVerifyEmailModal, hide: () => setShowVerifyEmailModal(false), sessionToken: sessionToken, onVerifiedOTP: () => {
65
64
  setShowVerifyEmailModal(false);
66
65
  setShowVerifyMobileModal(true);
67
66
  } })] }));
68
67
  };
69
- const RedirectToHomeModal = ({ redirectModal }) => {
70
- const router = useRouter();
68
+ const RedirectToHomeModal = ({ redirectModal, navigator }) => {
71
69
  useEffect(() => {
72
- router.push('/login');
70
+ navigator.pushAbsolute('/login');
73
71
  }, []);
74
- return (_jsxs(Modal, { title: "", open: redirectModal, showCloseButton: false, footer: null, children: [_jsx(Lottie
75
- /* @ts-ignore */
76
- , {
72
+ return (_jsxs(Modal, { title: "", open: redirectModal, showCloseButton: false, footer: null, children: [_jsx(Suspense, { fallback: _jsx("div", { className: "h-[150px] w-[250px]" }), children: _jsx(Lottie
77
73
  /* @ts-ignore */
78
- options: {
79
- animationData: redirectHome,
80
- loop: true,
81
- autoplay: true
82
- }, height: 150, width: 250 }), _jsxs(Flex, { direction: 'column', style: { marginTop: "-30px" }, children: [_jsx(Typography, { type: 'heading', size: 'h5', className: 'text-center mb-6', color: "text-gray-800", children: "Redirecting to the website homepage" }), _jsxs(Flex, { children: [_jsx(Info, { color: 'blue' }), _jsx(Typography, { type: 'body', size: 'small', className: 'text-center mb-6', color: "text-gray-800", children: "We have not stored any information you shared during registration." })] })] })] }));
74
+ , {
75
+ /* @ts-ignore */
76
+ options: {
77
+ animationData: redirectHome,
78
+ loop: true,
79
+ autoplay: true
80
+ }, height: 150, width: 250 }) }), _jsxs(Flex, { direction: 'column', style: { marginTop: "-30px" }, children: [_jsx(Typography, { type: 'heading', size: 'h5', className: 'text-center mb-6', color: "text-gray-800", children: "Redirecting to the website homepage" }), _jsxs(Flex, { children: [_jsx(Info, { color: 'blue' }), _jsx(Typography, { type: 'body', size: 'small', className: 'text-center mb-6', color: "text-gray-800", children: "We have not stored any information you shared during registration." })] })] })] }));
83
81
  };
84
82
  const VerifyMobileModal = ({ visible, hide, sessionToken, phone }) => {
85
83
  return (_jsx(ResponsiveModal, { title: 'Verify Mobile', open: visible, onClose: hide, onOpenChange: hide, maskClosable: false, showCloseButton: false, children: _jsx(VerifyMobile, { sessionToken: sessionToken, phone: phone }) }));
@@ -1,13 +1,13 @@
1
1
  import { useEffect, useCallback, useState } from "react";
2
- import { useRouter } from "next/router";
3
2
  import { useAuthPackageContext } from "../../../../../hooks/internal/useAuthPackageContext";
4
3
  import { RegistrationMethod } from "../../../../../enums";
5
4
  import { handleFacebookAuth } from "../../commonSocialAuth";
6
5
  import { showMessage } from "../../../../../helpers/show-message";
6
+ import { useNavigator } from "../../../../../hooks/useNavigator";
7
7
  export const useFacebookAuth = () => {
8
8
  const [error, setError] = useState(null);
9
9
  const { onLogin, socialLoginConfig, onRegistrationMethodChange, onTokenChange } = useAuthPackageContext();
10
- const router = useRouter();
10
+ const navigator = useNavigator();
11
11
  const facebookAppId = socialLoginConfig?.facebook?.webClientId;
12
12
  // Load Facebook SDK once
13
13
  useEffect(() => {
@@ -79,8 +79,7 @@ export const useFacebookAuth = () => {
79
79
  switch (result.status) {
80
80
  case "REGISTER":
81
81
  onRegistrationMethodChange(RegistrationMethod.SOCIAL);
82
- return router.push({
83
- pathname: "/registration",
82
+ return navigator.pushAbsolute("/registration", {
84
83
  query: {
85
84
  email,
86
85
  firstName: nameParts[0],
@@ -3,10 +3,10 @@ import { useGoogleLogin } from "react-google-login";
3
3
  import { useAuthPackageContext } from "../../../../../hooks/internal/useAuthPackageContext";
4
4
  import { showMessage } from "../../../../../helpers/show-message";
5
5
  import { RegistrationMethod } from "../../../../../enums";
6
- import { useRouter } from "next/router";
6
+ import { useNavigator } from "../../../../../hooks/useNavigator";
7
7
  import { handleGoogleAuth } from "../../commonSocialAuth";
8
8
  export const useGoogleAuth = () => {
9
- const router = useRouter();
9
+ const navigator = useNavigator();
10
10
  const { onLogin, socialLoginConfig, onRegistrationMethodChange, onTokenChange } = useAuthPackageContext();
11
11
  const googleAppId = socialLoginConfig?.google?.webClientId;
12
12
  const onSuccess = async (response) => {
@@ -20,8 +20,7 @@ export const useGoogleAuth = () => {
20
20
  switch (result.status) {
21
21
  case "REGISTER":
22
22
  onRegistrationMethodChange(RegistrationMethod.SOCIAL);
23
- return router.push({
24
- pathname: "/registration",
23
+ return navigator.pushAbsolute("/registration", {
25
24
  query: {
26
25
  email,
27
26
  firstName: givenName,
@@ -32,7 +32,7 @@ declare const AuthPackageContext: React.Context<AuthPackageContextValue | null>;
32
32
  * @param openChatSupport - Optional callback to open chat support
33
33
  * @param socialLoginConfig - Optional social login configuration
34
34
  */
35
- declare const AuthProvider: ({ children, LogoComponent, session, appConfig, onLogin, onLogout, onLaunchAuthSession, onRefreshSession, openChatSupport, socialLoginConfig }: AuthContextProps) => import("react/jsx-runtime").JSX.Element;
35
+ declare const AuthProvider: ({ children, router, basePath, LogoComponent, session, appConfig, onLogin, onLogout, onLaunchAuthSession, onRefreshSession, openChatSupport, socialLoginConfig }: AuthContextProps) => import("react/jsx-runtime").JSX.Element;
36
36
  export { AuthProvider };
37
37
  export default AuthContext;
38
38
  export { AuthPackageContext };
@@ -1,6 +1,21 @@
1
1
  import type React from 'react';
2
2
  import type { ClientProfile, MemberProfile, Profile, Partner, RegistrationMethod } from '../types/types';
3
3
  import type { SocialLoginsEnum } from '../enums';
4
+ /**
5
+ * Router adapter interface that host apps must implement.
6
+ * This allows the package to be framework-agnostic.
7
+ */
8
+ type RouterAdapter = {
9
+ push: (path: string, options?: {
10
+ query?: Record<string, string | string[] | undefined>;
11
+ }) => void;
12
+ replace: (path: string, options?: {
13
+ query?: Record<string, string | string[] | undefined>;
14
+ }) => void;
15
+ back: () => void;
16
+ getQuery: () => Record<string, string | string[] | undefined>;
17
+ getPathname: () => string;
18
+ };
4
19
  interface LoginResponse {
5
20
  token: string;
6
21
  member?: MemberProfile;
@@ -17,6 +32,10 @@ interface AppConfig {
17
32
  }
18
33
  interface AuthContextProps {
19
34
  children: React.ReactNode;
35
+ /** Router adapter for navigation - must be provided by host app */
36
+ router: RouterAdapter;
37
+ /** Base path where the auth module is mounted (e.g., '' for root or '/auth') */
38
+ basePath?: string;
20
39
  LogoComponent?: React.ComponentType | React.ReactElement;
21
40
  session: {
22
41
  token: string;
@@ -45,6 +64,10 @@ interface AuthContextValue {
45
64
  }
46
65
  interface AuthPackageContextValue {
47
66
  token: string;
67
+ /** Router adapter for navigation */
68
+ router: RouterAdapter;
69
+ /** Base path where the auth module is mounted */
70
+ basePath: string;
48
71
  LogoComponent?: React.ComponentType | React.ReactElement;
49
72
  appConfig: AppConfig;
50
73
  socialLoginConfig?: Partial<Record<SocialLoginsEnum, {
@@ -68,4 +91,4 @@ interface AuthConfigResponse {
68
91
  isInternational: boolean;
69
92
  defaultCircle: any;
70
93
  }
71
- export type { LoginResponse, AuthContextProps, AuthContextValue, MemberProfile, ClientProfile, AuthConfigResponse, Profile, Partner, AuthPackageContextValue, RefreshSessionResponse };
94
+ export type { LoginResponse, AuthContextProps, AuthContextValue, MemberProfile, ClientProfile, AuthConfigResponse, Profile, Partner, AuthPackageContextValue, RefreshSessionResponse, RouterAdapter };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * useNavigator Hook
3
+ *
4
+ * Provides framework-agnostic navigation capabilities.
5
+ * Uses the RouterAdapter provided by the host app through AuthProvider.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const navigator = useNavigator();
10
+ *
11
+ * // Relative navigation (uses basePath)
12
+ * navigator.push('/registration');
13
+ *
14
+ * // Absolute navigation (ignores basePath)
15
+ * navigator.pushAbsolute('/dashboard');
16
+ *
17
+ * // With query params
18
+ * navigator.push('/registration', { query: { email: 'test@example.com' } });
19
+ *
20
+ * // Access query params
21
+ * const { email } = navigator.query;
22
+ * ```
23
+ */
24
+ declare const useNavigator: () => {
25
+ /**
26
+ * Navigate to a path relative to basePath
27
+ */
28
+ push: (path: string, options?: {
29
+ query?: Record<string, string | string[] | undefined>;
30
+ }) => void;
31
+ /**
32
+ * Replace current path with a path relative to basePath
33
+ */
34
+ replace: (path: string, options?: {
35
+ query?: Record<string, string | string[] | undefined>;
36
+ }) => void;
37
+ /**
38
+ * Navigate to an absolute path (ignores basePath)
39
+ */
40
+ pushAbsolute: (path: string, options?: {
41
+ query?: Record<string, string | string[] | undefined>;
42
+ }) => void;
43
+ /**
44
+ * Replace with an absolute path (ignores basePath)
45
+ */
46
+ replaceAbsolute: (path: string, options?: {
47
+ query?: Record<string, string | string[] | undefined>;
48
+ }) => void;
49
+ /**
50
+ * Go back to the previous page
51
+ */
52
+ back: () => void;
53
+ /**
54
+ * Get current query parameters
55
+ */
56
+ readonly query: Record<string, string | string[] | undefined>;
57
+ /**
58
+ * Get current pathname
59
+ */
60
+ readonly pathname: string;
61
+ /**
62
+ * The base path where the auth module is mounted
63
+ */
64
+ basePath: string;
65
+ };
66
+ export { useNavigator };
@@ -3,6 +3,7 @@ export { default as AuthContext } from './contexts/AuthContext';
3
3
  export * from './contexts/type';
4
4
  export * from './hooks/useAuthContext';
5
5
  export * from './hooks/useRequest';
6
+ export * from './hooks/useNavigator';
6
7
  export * from './api/axiosClient';
7
8
  export * from './navigator';
8
9
  export * from './screens/Login/components/LoginWebComponent/index';
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "description": "Truworth Auth Package for React Native and Web",
7
- "version": "1.2.11",
7
+ "version": "2.0.0",
8
8
  "main": "build/src/index.js",
9
9
  "types": "build/types/index.d.ts",
10
10
  "files": [
@@ -82,7 +82,6 @@
82
82
  "lodash": ">=4.17.21",
83
83
  "lottie-react-native": ">=6.7.2",
84
84
  "lucide-react": ">=0.483.0",
85
- "next": ">=13.4.19",
86
85
  "react": ">=18.2.0",
87
86
  "react-dom": ">=18.2.0",
88
87
  "react-native": ">=0.74.5",