@truworth/twc-auth 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/index.js +3 -0
- package/build/src/screens/CreatePassword/hooks/internal/useCreatePassword.js +11 -4
- package/build/src/screens/CreatePassword/index.js +4 -31
- package/build/src/screens/PartnerSSO/PartnerLogin/components/PartnerLoginWebComponent/index.js +77 -0
- package/build/src/screens/PartnerSSO/PartnerLogin/hooks/internal/usePartnerLogin.js +50 -0
- package/build/src/screens/PartnerSSO/PartnerLogin/index.js +2 -0
- package/build/src/screens/PartnerSSO/PartnerLogin/types.js +1 -0
- package/build/src/screens/PartnerSSO/PartnerRegistration/components/PartnerRegistrationWebComponent/index.js +203 -0
- package/build/src/screens/PartnerSSO/PartnerRegistration/hooks/internal/usePartnerRegistration.js +90 -0
- package/build/src/screens/PartnerSSO/PartnerRegistration/index.js +2 -0
- package/build/src/screens/PartnerSSO/PartnerRegistration/types.js +1 -0
- package/build/src/screens/PartnerSSO/index.js +2 -0
- package/build/src/screens/UserConsent/index.js +12 -7
- package/build/types/index.d.ts +2 -0
- package/build/types/screens/CreatePassword/hooks/internal/useCreatePassword.d.ts +12 -1
- package/build/types/screens/PartnerSSO/PartnerLogin/components/PartnerLoginWebComponent/index.d.ts +25 -0
- package/build/types/screens/PartnerSSO/PartnerLogin/hooks/internal/usePartnerLogin.d.ts +8 -0
- package/build/types/screens/PartnerSSO/PartnerLogin/index.d.ts +2 -0
- package/build/types/screens/PartnerSSO/PartnerLogin/types.d.ts +35 -0
- package/build/types/screens/PartnerSSO/PartnerRegistration/components/PartnerRegistrationWebComponent/index.d.ts +24 -0
- package/build/types/screens/PartnerSSO/PartnerRegistration/hooks/internal/usePartnerRegistration.d.ts +8 -0
- package/build/types/screens/PartnerSSO/PartnerRegistration/index.d.ts +2 -0
- package/build/types/screens/PartnerSSO/PartnerRegistration/types.d.ts +63 -0
- package/build/types/screens/PartnerSSO/index.d.ts +2 -0
- package/package.json +4 -1
package/build/src/index.js
CHANGED
|
@@ -14,6 +14,9 @@ export * from './navigator';
|
|
|
14
14
|
export * from './screens/Login/components/LoginWebComponent/index';
|
|
15
15
|
export * from './screens/SignUp/components/SignUpWebComponent/index';
|
|
16
16
|
export * from './screens/SSOLogin/SSOCallback/components/SSOCallbackComponent/index';
|
|
17
|
+
// export partner SSO components
|
|
18
|
+
export * from './screens/PartnerSSO/PartnerLogin/components/PartnerLoginWebComponent';
|
|
19
|
+
export * from './screens/PartnerSSO/PartnerRegistration/components/PartnerRegistrationWebComponent';
|
|
17
20
|
// export profile components
|
|
18
21
|
export * from './screens/Profile';
|
|
19
22
|
// export constants
|
|
@@ -4,12 +4,17 @@ import { defaultPolicy } from "../../../../constants/defaultPolicy";
|
|
|
4
4
|
* @internal
|
|
5
5
|
* Internal hook for managing CreatePassword screen state and auth context integration.
|
|
6
6
|
* Not exposed to package consumers.
|
|
7
|
+
*
|
|
8
|
+
* Both web and native use the same pattern:
|
|
9
|
+
* - Hook manages password state internally
|
|
10
|
+
* - Use handlePassword/handleConfirmPassword to update state
|
|
11
|
+
* - Optional initial values for preserving state when navigating back
|
|
7
12
|
*/
|
|
8
|
-
const useCreatePassword = () => {
|
|
13
|
+
const useCreatePassword = (props) => {
|
|
9
14
|
const [passwordVisible, setPasswordVisible] = useState(false);
|
|
10
15
|
const [confirmPasswordVisible, setConfirmPasswordVisible] = useState(false);
|
|
11
|
-
const [password, setPassword] = useState('');
|
|
12
|
-
const [confirmPassword, setConfirmPassword] = useState('');
|
|
16
|
+
const [password, setPassword] = useState(props?.initialPassword || '');
|
|
17
|
+
const [confirmPassword, setConfirmPassword] = useState(props?.initialConfirmPassword || '');
|
|
13
18
|
const [maxLength, setMaxLength] = useState(defaultPolicy.maxLength);
|
|
14
19
|
const [criteria, setCriteria] = useState({});
|
|
15
20
|
const handlePassword = (text) => {
|
|
@@ -23,7 +28,9 @@ const useCreatePassword = () => {
|
|
|
23
28
|
setConfirmPassword('');
|
|
24
29
|
onResult();
|
|
25
30
|
};
|
|
26
|
-
const
|
|
31
|
+
const allCriteriaMet = Object.keys(criteria).length > 0 && Object.keys(criteria).every(c => criteria[c]);
|
|
32
|
+
const passwordsMatch = password.length > 0 && password === confirmPassword;
|
|
33
|
+
const isContinueDisabled = !allCriteriaMet || !passwordsMatch;
|
|
27
34
|
return {
|
|
28
35
|
password, setPassword,
|
|
29
36
|
confirmPassword, setConfirmPassword,
|
|
@@ -1,43 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Button, Flex,
|
|
2
|
+
import { Button, Flex, PasswordInput, Typography } from "@truworth/twc-web-design";
|
|
3
3
|
import { useCreatePassword } from "./hooks/internal/useCreatePassword";
|
|
4
4
|
import { ScreenLayout } from "../../components/ScreenLayout";
|
|
5
5
|
import { PasswordCriteria } from "../../components/PasswordCriteria";
|
|
6
6
|
const CreatePassword = ({ userDetails, handleBack, onContinue }) => {
|
|
7
7
|
const { countryCode, email } = userDetails;
|
|
8
|
-
const
|
|
9
|
-
defaultValues: {
|
|
10
|
-
password: userDetails.password || '',
|
|
11
|
-
confirmPassword: userDetails.confirmPassword || '',
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
const { password, confirmPassword } = form.watch();
|
|
15
|
-
const { criteria, setCriteria, maxLength, setMaxLength, handleSkip, isContinueDisabled } = useCreatePassword();
|
|
8
|
+
const { password, confirmPassword, handlePassword, handleConfirmPassword, passwordVisible, setPasswordVisible, confirmPasswordVisible, setConfirmPasswordVisible, criteria, setCriteria, maxLength, setMaxLength, handleSkip, isContinueDisabled } = useCreatePassword();
|
|
16
9
|
return (_jsx(ScreenLayout, { title: _jsxs(Flex, { justify: "between", align: "center", children: [_jsx(Typography, { type: "heading", size: "h5", children: "Create your Password" }), countryCode == '91' &&
|
|
17
|
-
_jsx(Button, { label: "Skip Now", variant: "link", onClick: () => handleSkip({ onResult: onContinue }) })] }), subTitle: "Use a password that's easy to remember and fulfills all the requirements listed below.", buttonProps: {
|
|
10
|
+
_jsx(Button, { label: "Skip Now", variant: "link", onClick: () => handleSkip({ onResult: () => onContinue('', '') }) })] }), subTitle: "Use a password that's easy to remember and fulfills all the requirements listed below.", buttonProps: {
|
|
18
11
|
label: 'Continue',
|
|
19
12
|
onClick: () => onContinue(password, confirmPassword),
|
|
20
13
|
disabled: isContinueDisabled
|
|
21
|
-
}, onPressBack: handleBack, children: _jsxs(
|
|
22
|
-
{
|
|
23
|
-
required: true,
|
|
24
|
-
message: 'Please enter your password'
|
|
25
|
-
}
|
|
26
|
-
], children: _jsx(PasswordInput, { placeholder: "Enter password", value: password, maxLength: maxLength, showStrengthIndicator: false }) }), _jsx(PasswordCriteria, { email: email, password: password, criteria: criteria, onCriteriaChange: setCriteria, onMaxLengthChange: setMaxLength }), _jsx(Form.Item, { name: "confirmPassword", label: "Confirm Password", className: "mt-6", normalize: (value) => value.replace(/\s/g, ''), rules: [
|
|
27
|
-
{
|
|
28
|
-
required: true,
|
|
29
|
-
message: 'Please confirm your password',
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
validator: async (_, value) => {
|
|
33
|
-
const password = form.getValues('password');
|
|
34
|
-
if (!value)
|
|
35
|
-
return;
|
|
36
|
-
if (!value || password !== value) {
|
|
37
|
-
throw new Error('Passwords don’t match');
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
], children: _jsx(PasswordInput, { className: "mb-6", value: confirmPassword, placeholder: "Re-enter password", maxLength: maxLength, showStrengthIndicator: false }) })] }) }));
|
|
14
|
+
}, onPressBack: handleBack, children: _jsxs("div", { style: { width: '100%' }, children: [_jsxs("div", { style: { marginBottom: '8px', width: '100%' }, children: [_jsx(Typography, { type: "body", size: "small", className: "mb-1 font-medium", children: "Enter Password" }), _jsx("div", { style: { width: '100%' }, children: _jsx(PasswordInput, { placeholder: "Enter password", value: password, onChange: (value) => handlePassword(value), maxLength: maxLength, showStrengthIndicator: false, style: { width: '100%' } }) })] }), _jsx(PasswordCriteria, { email: email, password: password, criteria: criteria, onCriteriaChange: setCriteria, onMaxLengthChange: setMaxLength }), _jsxs("div", { style: { marginTop: '24px', marginBottom: '24px', width: '100%' }, children: [_jsx(Typography, { type: "body", size: "small", className: "mb-1 font-medium", children: "Confirm Password" }), _jsx("div", { style: { width: '100%' }, children: _jsx(PasswordInput, { placeholder: "Re-enter password", value: confirmPassword, onChange: (value) => handleConfirmPassword(value), maxLength: maxLength, showStrengthIndicator: false, style: { width: '100%' } }) }), confirmPassword && password !== confirmPassword && (_jsx(Typography, { type: "body", size: "tiny", className: "text-red-500 mt-1", children: "Passwords don't match" }))] })] }) }));
|
|
42
15
|
};
|
|
43
16
|
export default CreatePassword;
|
package/build/src/screens/PartnerSSO/PartnerLogin/components/PartnerLoginWebComponent/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { Suspense, lazy, useEffect, useState } from 'react';
|
|
3
|
+
import { Flex, Typography } from '@truworth/twc-web-design';
|
|
4
|
+
import { usePartnerLogin } from '../../hooks/internal/usePartnerLogin';
|
|
5
|
+
import { useNavigator } from '../../../../../hooks/useNavigator';
|
|
6
|
+
import redirectAnimation from '../../../../../../assets/animation/redirect-home.json';
|
|
7
|
+
const Lottie = lazy(() => import('react-lottie'));
|
|
8
|
+
/**
|
|
9
|
+
* PartnerLoginWebComponent
|
|
10
|
+
*
|
|
11
|
+
* Handles partner SSO authentication flow for web applications.
|
|
12
|
+
* Extracts token and partner from URL query params, authenticates with backend,
|
|
13
|
+
* and redirects to the specified path on success.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* // In your app's partner login route (react-router - pass partner from useParams)
|
|
18
|
+
* const { partner } = useParams();
|
|
19
|
+
* <PartnerLoginWebComponent partner={partner} />
|
|
20
|
+
*
|
|
21
|
+
* // In Next.js (partner comes from query params automatically)
|
|
22
|
+
* <PartnerLoginWebComponent />
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
const PartnerLoginWebComponent = ({ partner: partnerProp }) => {
|
|
26
|
+
const navigator = useNavigator();
|
|
27
|
+
const [partner, setPartner] = useState(partnerProp || '');
|
|
28
|
+
const [token, setToken] = useState('');
|
|
29
|
+
const [path, setPath] = useState(undefined);
|
|
30
|
+
const [isInitialized, setIsInitialized] = useState(false);
|
|
31
|
+
// Clear localStorage only ONCE on initial mount
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (typeof window !== 'undefined') {
|
|
34
|
+
try {
|
|
35
|
+
localStorage.clear();
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
// Ignore localStorage errors
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
// Extract query params
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (isInitialized)
|
|
45
|
+
return;
|
|
46
|
+
const query = navigator.query;
|
|
47
|
+
// Use prop if provided, otherwise try query params
|
|
48
|
+
const partnerValue = partnerProp || (typeof query.partner === 'string' ? query.partner : '');
|
|
49
|
+
const tokenValue = typeof query.token === 'string' ? query.token : '';
|
|
50
|
+
const pathValue = typeof query.path === 'string' ? query.path : undefined;
|
|
51
|
+
if (partnerValue && tokenValue) {
|
|
52
|
+
setPartner(partnerValue);
|
|
53
|
+
setToken(tokenValue);
|
|
54
|
+
setPath(pathValue);
|
|
55
|
+
setIsInitialized(true);
|
|
56
|
+
}
|
|
57
|
+
}, [navigator.query, partnerProp, isInitialized]);
|
|
58
|
+
const { isLoading, error } = usePartnerLogin({ partner, token, path });
|
|
59
|
+
// If there's an error, redirect back after a delay
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (error) {
|
|
62
|
+
setTimeout(() => {
|
|
63
|
+
navigator.back();
|
|
64
|
+
}, 2000);
|
|
65
|
+
}
|
|
66
|
+
}, [error, navigator]);
|
|
67
|
+
return (_jsx(Flex, { justify: 'center', align: 'center', className: 'h-[100vh]', children: _jsxs("div", { className: 'text-center', children: [_jsx(Suspense, { fallback: _jsx("div", { className: "h-[230px] w-[250px]" }), children: _jsx(Lottie
|
|
68
|
+
//@ts-ignore
|
|
69
|
+
, {
|
|
70
|
+
//@ts-ignore
|
|
71
|
+
options: {
|
|
72
|
+
animationData: redirectAnimation,
|
|
73
|
+
loop: true,
|
|
74
|
+
autoplay: true,
|
|
75
|
+
}, height: 230, width: 250 }) }), error ? (_jsxs(_Fragment, { children: [_jsx(Typography, { size: 'h6', type: 'heading', color: 'error', children: error }), _jsx(Typography, { size: 'small', type: 'body', color: 'gray-400', className: 'mt-2', children: "Redirecting back..." })] })) : (_jsx(_Fragment, { children: _jsx(Typography, { size: 'h6', type: 'heading', color: 'gray-400', children: "Authenticating and Redirecting ..." }) }))] }) }));
|
|
76
|
+
};
|
|
77
|
+
export { PartnerLoginWebComponent };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { axiosClient } from '../../../../../api/axiosClient';
|
|
3
|
+
import { showMessage } from '../../../../../helpers/show-message';
|
|
4
|
+
import { useAuthPackageContext } from '../../../../../hooks/internal/useAuthPackageContext';
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
* Hook for managing Partner SSO Login authentication.
|
|
8
|
+
* This hook is not exposed to package consumers.
|
|
9
|
+
*/
|
|
10
|
+
const usePartnerLogin = ({ partner, token, path }) => {
|
|
11
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
const [hasAttempted, setHasAttempted] = useState(false);
|
|
14
|
+
const { onLogin, onTokenChange } = useAuthPackageContext();
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
// Only attempt authentication once when we have partner and token
|
|
17
|
+
if (hasAttempted || !partner || !token) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const authenticate = async () => {
|
|
21
|
+
setHasAttempted(true);
|
|
22
|
+
setIsLoading(true);
|
|
23
|
+
setError(null);
|
|
24
|
+
try {
|
|
25
|
+
const response = await axiosClient({
|
|
26
|
+
url: `/auth/partners/${partner}/authenticate`,
|
|
27
|
+
method: 'POST',
|
|
28
|
+
data: { token },
|
|
29
|
+
});
|
|
30
|
+
const { token: authToken, member } = response.data;
|
|
31
|
+
// Update auth context
|
|
32
|
+
onTokenChange(authToken);
|
|
33
|
+
// Call onLogin callback - this will store token and redirect
|
|
34
|
+
await onLogin({
|
|
35
|
+
token: authToken,
|
|
36
|
+
member,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const errorMessage = err?.response?.data?.errors?.[0]?.message || 'Authentication failed';
|
|
41
|
+
setError(errorMessage);
|
|
42
|
+
showMessage({ message: errorMessage });
|
|
43
|
+
setIsLoading(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
authenticate();
|
|
47
|
+
}, [partner, token, path, hasAttempted, onLogin, onTokenChange]);
|
|
48
|
+
return { isLoading, error };
|
|
49
|
+
};
|
|
50
|
+
export { usePartnerLogin };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import { Button, Col, CustomCheckbox, DatePicker, Flex, Form, RadioGroup, Row, TextInput, Typography, useForm, validationPatterns, } from '@truworth/twc-web-design';
|
|
4
|
+
import { CalendarDays } from 'lucide-react';
|
|
5
|
+
import ReCAPTCHA from 'react-google-recaptcha';
|
|
6
|
+
import dayjs from 'dayjs';
|
|
7
|
+
import moment from 'moment';
|
|
8
|
+
import { ScreenLayout } from '../../../../../components/ScreenLayout';
|
|
9
|
+
import { CountryCodeDropdown } from '../../../../CountryCode/components/CountryCodeDropdown';
|
|
10
|
+
import { usePartnerRegistration } from '../../hooks/internal/usePartnerRegistration';
|
|
11
|
+
import { useNavigator } from '../../../../../hooks/useNavigator';
|
|
12
|
+
import { useAuthPackageContext } from '../../../../../hooks/internal/useAuthPackageContext';
|
|
13
|
+
import { showMessage } from '../../../../../helpers/show-message';
|
|
14
|
+
import { CDN_IMAGES_URL } from '../../../../../constants/cdn-url';
|
|
15
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
16
|
+
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
17
|
+
/**
|
|
18
|
+
* PartnerRegistrationWebComponent
|
|
19
|
+
*
|
|
20
|
+
* Handles partner SSO registration flow for web applications.
|
|
21
|
+
* Pre-fills form with data from JWT token and collects missing information.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* // In your app's partner registration route (react-router - pass partner from useParams)
|
|
26
|
+
* const { partner } = useParams();
|
|
27
|
+
* <PartnerRegistrationWebComponent partner={partner} />
|
|
28
|
+
*
|
|
29
|
+
* // In Next.js (partner comes from query params automatically)
|
|
30
|
+
* <PartnerRegistrationWebComponent />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
const PartnerRegistrationWebComponent = ({ partner: partnerProp }) => {
|
|
34
|
+
const navigator = useNavigator();
|
|
35
|
+
const { appConfig } = useAuthPackageContext();
|
|
36
|
+
// Query params state
|
|
37
|
+
const [partner, setPartner] = useState(partnerProp || '');
|
|
38
|
+
const [token, setToken] = useState('');
|
|
39
|
+
const [path, setPath] = useState(undefined);
|
|
40
|
+
// Form state
|
|
41
|
+
const [selectedCountry, setSelectedCountry] = useState({
|
|
42
|
+
countryCode: 'in',
|
|
43
|
+
phoneCode: '91',
|
|
44
|
+
name: 'INDIA',
|
|
45
|
+
});
|
|
46
|
+
const [isHuman, setIsHuman] = useState(!isProduction);
|
|
47
|
+
const [termsAndConditions, setTermsAndConditions] = useState(false);
|
|
48
|
+
const [emailExists, setEmailExists] = useState(false);
|
|
49
|
+
// Extract query params on mount
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
const query = navigator.query;
|
|
52
|
+
// Use prop if provided, otherwise try query params
|
|
53
|
+
if (!partnerProp && typeof query.partner === 'string') {
|
|
54
|
+
setPartner(query.partner);
|
|
55
|
+
}
|
|
56
|
+
if (typeof query.token === 'string') {
|
|
57
|
+
setToken(query.token);
|
|
58
|
+
}
|
|
59
|
+
if (typeof query.path === 'string') {
|
|
60
|
+
setPath(query.path);
|
|
61
|
+
}
|
|
62
|
+
// Clear localStorage on partner registration (clean slate)
|
|
63
|
+
if (typeof window !== 'undefined') {
|
|
64
|
+
try {
|
|
65
|
+
localStorage.clear();
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
console.warn('Failed to clear localStorage:', e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, [navigator.query, partnerProp]);
|
|
72
|
+
// Update partner if prop changes
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (partnerProp) {
|
|
75
|
+
setPartner(partnerProp);
|
|
76
|
+
}
|
|
77
|
+
}, [partnerProp]);
|
|
78
|
+
const { isLoading, tokenData, register, checkEmailExists } = usePartnerRegistration({
|
|
79
|
+
partner,
|
|
80
|
+
token,
|
|
81
|
+
path,
|
|
82
|
+
});
|
|
83
|
+
const form = useForm({
|
|
84
|
+
liveValidation: true,
|
|
85
|
+
defaultValues: {
|
|
86
|
+
firstName: '',
|
|
87
|
+
lastName: '',
|
|
88
|
+
dateOfBirth: undefined,
|
|
89
|
+
email: '',
|
|
90
|
+
phone: '',
|
|
91
|
+
gender: '',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const values = form.watch();
|
|
95
|
+
// Pre-fill form with token data
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (tokenData) {
|
|
98
|
+
form.reset({
|
|
99
|
+
firstName: tokenData.firstName?.replace(/\s/g, '') || '',
|
|
100
|
+
lastName: tokenData.lastName?.replace(/\s/g, '') || '',
|
|
101
|
+
email: tokenData.email || '',
|
|
102
|
+
gender: tokenData.gender || '',
|
|
103
|
+
dateOfBirth: tokenData.dob ? moment(tokenData.dob, 'YYYY-MM-DD').toDate() : undefined,
|
|
104
|
+
phone: '',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}, [tokenData, form]);
|
|
108
|
+
const handleCaptcha = (value) => {
|
|
109
|
+
setIsHuman(Boolean(value));
|
|
110
|
+
};
|
|
111
|
+
const handleEmailBlur = async () => {
|
|
112
|
+
if (values.email && EMAIL_REGEX.test(values.email)) {
|
|
113
|
+
const exists = await checkEmailExists(values.email);
|
|
114
|
+
setEmailExists(exists);
|
|
115
|
+
if (exists) {
|
|
116
|
+
showMessage({ message: 'An account already exists with this email ID' });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const disabledDate = (current) => {
|
|
121
|
+
const currentDate = dayjs(current);
|
|
122
|
+
if (!currentDate.isValid()) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const thirteenYearsAgo = dayjs().subtract(13, 'years');
|
|
126
|
+
const oldestAllowedDate = dayjs('1900-01-01');
|
|
127
|
+
return currentDate.isAfter(thirteenYearsAgo) || currentDate.isBefore(oldestAllowedDate);
|
|
128
|
+
};
|
|
129
|
+
const handleSubmit = async () => {
|
|
130
|
+
if (!values.gender) {
|
|
131
|
+
showMessage({ message: 'Please select your gender' });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const formData = {
|
|
135
|
+
email: values.email.toLowerCase(),
|
|
136
|
+
countryCode: selectedCountry.phoneCode,
|
|
137
|
+
mobile: values.phone,
|
|
138
|
+
firstName: values.firstName,
|
|
139
|
+
lastName: values.lastName,
|
|
140
|
+
gender: values.gender,
|
|
141
|
+
dateOfBirth: values.dateOfBirth
|
|
142
|
+
? moment(values.dateOfBirth).format('YYYY-MM-DD')
|
|
143
|
+
: '',
|
|
144
|
+
};
|
|
145
|
+
await register(formData);
|
|
146
|
+
};
|
|
147
|
+
const isFormValid = values.firstName &&
|
|
148
|
+
values.firstName.length >= 3 &&
|
|
149
|
+
values.lastName &&
|
|
150
|
+
values.lastName.length >= 3 &&
|
|
151
|
+
values.gender &&
|
|
152
|
+
values.dateOfBirth &&
|
|
153
|
+
values.email &&
|
|
154
|
+
EMAIL_REGEX.test(values.email) &&
|
|
155
|
+
!emailExists &&
|
|
156
|
+
values.phone &&
|
|
157
|
+
(selectedCountry.phoneCode === '91' ? values.phone.length === 10 : values.phone.length >= 6) &&
|
|
158
|
+
termsAndConditions &&
|
|
159
|
+
isHuman;
|
|
160
|
+
const appName = appConfig?.appName || 'The Wellness Corner';
|
|
161
|
+
const termsAndConditionsUrl = appConfig?.termsAndConditionsUrl || '/privacy-policy';
|
|
162
|
+
return (_jsxs(Flex, { direction: "column", className: "min-h-screen", children: [_jsx(Flex, { justify: "center", align: "center", className: "py-6 border-b", children: _jsx(Typography, { type: "heading", size: "h4", color: "primary", children: appName }) }), _jsxs(Flex, { direction: "row", className: "flex-1", children: [_jsxs(Flex, { direction: "column", justify: "center", className: "hidden lg:flex lg:w-1/2 bg-primary-50 p-12", children: [_jsxs(Typography, { type: "heading", size: "h2", className: "mb-4", children: ["Welcome To", _jsx("br", {}), appName] }), _jsx(Typography, { type: "body", size: "large", color: "gray-600", children: "We just need a few details to get you started. It will only take less than a minute. Please note we only need these details to communicate with you when you avail any of our services." })] }), _jsx(Flex, { direction: "column", className: "w-full lg:w-1/2 p-6 lg:p-12 overflow-y-auto", children: _jsx(ScreenLayout, { title: "Complete Your Registration", subTitle: "Tell us more about you so we can set up your account.", buttonProps: {
|
|
163
|
+
loading: isLoading,
|
|
164
|
+
label: 'Register',
|
|
165
|
+
onClick: handleSubmit,
|
|
166
|
+
disabled: !isFormValid,
|
|
167
|
+
}, children: _jsxs(Form, { form: form, className: "w-full", children: [_jsx(Typography, { type: "heading", size: "h6", className: "mb-4", children: "Account Details" }), _jsx(Row, { gutter: 18, className: "py-0 my-0", children: _jsxs(Col, { xs: 24, children: [_jsx(Form.Item, { label: "Email Address", rules: [
|
|
168
|
+
{ required: true, message: 'Please enter your email' },
|
|
169
|
+
{ pattern: EMAIL_REGEX, message: 'Invalid email address' },
|
|
170
|
+
], ...form.register('email', {
|
|
171
|
+
onBlur: handleEmailBlur,
|
|
172
|
+
}), children: _jsx(TextInput, { type: "email", placeholder: "Enter your email address", className: emailExists ? 'border-red-500' : '' }) }), emailExists && (_jsx(Typography, { type: "utility", size: "small", color: "red-600", className: "-mt-4 mb-4", children: "An account with this email ID already exists" }))] }) }), _jsx(Row, { gutter: 18, className: "py-0 my-0", children: _jsx(Col, { xs: 24, children: _jsx(Form.Item, { label: "Mobile Number", rules: [{ required: true, message: 'Please enter your mobile number' }], ...form.register('phone'), children: _jsx(Flex, { className: "border border-gray-400 rounded-md", children: _jsx(TextInput, { type: "tel", placeholder: "Mobile number", maxLength: selectedCountry.phoneCode === '91' ? 10 : 14, className: "py-4 border-0 md:text-base", addonBefore: _jsx(CountryCodeDropdown, { selectedCountry: selectedCountry, handleSelect: (country) => {
|
|
173
|
+
setSelectedCountry(country);
|
|
174
|
+
form.setValue('phone', '');
|
|
175
|
+
} }) }) }) }) }) }), _jsx(Typography, { type: "heading", size: "h6", className: "mb-4 mt-6", children: "Personal Details" }), _jsxs(Row, { gutter: 18, className: "py-0 my-0", children: [_jsx(Col, { xs: 24, md: 12, children: _jsx(Form.Item, { label: "First Name", rules: [
|
|
176
|
+
{ required: true, message: 'Please enter your first name' },
|
|
177
|
+
{ pattern: validationPatterns.nameOnly, message: 'Name can only contain letters' },
|
|
178
|
+
{
|
|
179
|
+
validator: async (_, value) => {
|
|
180
|
+
if (value && value.length < 3) {
|
|
181
|
+
throw new Error('Name must be at least 3 characters');
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
], normalize: (value) => (value ?? '').replace(/[^a-zA-Z]/g, ''), ...form.register('firstName'), children: _jsx(TextInput, { placeholder: "Enter your first name" }) }) }), _jsx(Col, { xs: 24, md: 12, children: _jsx(Form.Item, { label: "Last Name", rules: [
|
|
186
|
+
{ required: true, message: 'Please enter your last name' },
|
|
187
|
+
{ pattern: validationPatterns.nameOnly, message: 'Name can only contain letters' },
|
|
188
|
+
{
|
|
189
|
+
validator: async (_, value) => {
|
|
190
|
+
if (value && value.length < 3) {
|
|
191
|
+
throw new Error('Name must be at least 3 characters');
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
], normalize: (value) => (value ?? '').replace(/[^a-zA-Z]/g, ''), ...form.register('lastName'), children: _jsx(TextInput, { placeholder: "Enter your last name" }) }) })] }), _jsxs(Row, { gutter: 18, className: "py-0 mt-4 relative", children: [_jsx(Col, { xs: 18, md: 19, children: _jsx(Typography, { type: "body", size: "large", className: "text-gray-700", children: "Your biological sex at the time of birth" }) }), _jsxs(Col, { xs: 6, md: 5, className: "flex justify-end group", children: [_jsx(Typography, { type: "body", size: "large", className: "text-primary cursor-pointer whitespace-nowrap", children: "Know More" }), _jsxs(Flex, { className: "hidden group-hover:block w-full absolute left-2 top-6 rounded-md p-4 z-10 text-gray-950 bg-white shadow-[0_0_10px_0_rgba(0,0,0,0.3)]", children: [_jsxs(Flex, { className: "p-0 mb-3 gap-3", children: [_jsx("img", { src: `${CDN_IMAGES_URL}/registration/gender-diversity.svg`, alt: "gender diversity", width: 44, height: 44, className: "h-auto" }), _jsxs(Typography, { type: "body", size: "large", className: "text-gray-800", children: [appName, " respects Gender Diversity & Inclusion!"] })] }), _jsxs(Typography, { type: "body", size: "small", className: "text-gray-500", children: [appName, " aims to provide you with a personalized wellness experience. This requires us to incorporate certain algorithms based on protocols provided by established health & medical institutions around the world. These protocols are based on your biological sex at birth."] })] })] })] }), _jsx(Row, { className: "py-0 mb-5", children: _jsx(Col, { children: _jsx(Form.Item, { className: "mt-4", rules: [{ required: true, message: 'Please select your biological sex' }], ...form.register('gender'), children: _jsx(RadioGroup, { orientation: "horizontal", options: [
|
|
196
|
+
{ label: 'Male', value: 'M' },
|
|
197
|
+
{ label: 'Female', value: 'F' },
|
|
198
|
+
] }) }) }) }), _jsx(Row, { gutter: 18, className: "py-0 my-5", children: _jsx(Col, { xs: 24, children: _jsx(Form.Item, { label: "Date of Birth", rules: [{ required: true, message: 'Please select your date of birth' }], ...form.register('dateOfBirth'), children: _jsx(DatePicker, { required: true, size: "middle", format: "dd/MM/yyyy", placeholder: "DD/MM/YYYY",
|
|
199
|
+
/* @ts-ignore */
|
|
200
|
+
disabledDate: disabledDate, iconPosition: "right", defaultPickerValue: dayjs('1990-01-01').toDate(), suffixIcon: _jsx(CalendarDays, { className: "-mt-2" }) }) }) }) }), isProduction &&
|
|
201
|
+
(process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY ? (_jsx(Row, { gutter: 10, children: _jsx(Col, { span: 24, children: _jsx(ReCAPTCHA, { className: "g-recaptcha mt-4", sitekey: process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY, onChange: handleCaptcha, onExpired: () => setIsHuman(false), onErrored: () => setIsHuman(false), size: "normal" }) }) })) : (_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: () => setTermsAndConditions((prev) => !prev), 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] }) }) }) })] }) }) })] })] }));
|
|
202
|
+
};
|
|
203
|
+
export { PartnerRegistrationWebComponent };
|
package/build/src/screens/PartnerSSO/PartnerRegistration/hooks/internal/usePartnerRegistration.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { jwtDecode } from 'jwt-decode';
|
|
3
|
+
import { axiosClient } from '../../../../../api/axiosClient';
|
|
4
|
+
import { showMessage } from '../../../../../helpers/show-message';
|
|
5
|
+
import { useAuthPackageContext } from '../../../../../hooks/internal/useAuthPackageContext';
|
|
6
|
+
/**
|
|
7
|
+
* @internal
|
|
8
|
+
* Hook for managing Partner SSO Registration.
|
|
9
|
+
* This hook is not exposed to package consumers.
|
|
10
|
+
*/
|
|
11
|
+
const usePartnerRegistration = ({ partner, token, path, }) => {
|
|
12
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
13
|
+
const [error, setError] = useState(null);
|
|
14
|
+
const [tokenData, setTokenData] = useState(null);
|
|
15
|
+
const { onLogin, onTokenChange } = useAuthPackageContext();
|
|
16
|
+
// Decode JWT token to pre-fill form fields
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (token) {
|
|
19
|
+
try {
|
|
20
|
+
const decoded = jwtDecode(token);
|
|
21
|
+
setTokenData(decoded);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.warn('Failed to decode partner token:', err);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}, [token]);
|
|
28
|
+
const checkEmailExists = useCallback(async (email) => {
|
|
29
|
+
try {
|
|
30
|
+
await axiosClient({
|
|
31
|
+
url: '/auth/email-exists',
|
|
32
|
+
method: 'POST',
|
|
33
|
+
data: { email },
|
|
34
|
+
});
|
|
35
|
+
// If request succeeds, email exists
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// If request fails (404), email doesn't exist
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}, []);
|
|
43
|
+
const register = useCallback(async (formData) => {
|
|
44
|
+
if (!partner || !token) {
|
|
45
|
+
showMessage({ message: 'Missing partner or token information' });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
setIsLoading(true);
|
|
49
|
+
setError(null);
|
|
50
|
+
try {
|
|
51
|
+
const response = await axiosClient({
|
|
52
|
+
url: `/auth/partners/${partner}/registration`,
|
|
53
|
+
method: 'POST',
|
|
54
|
+
data: {
|
|
55
|
+
...formData,
|
|
56
|
+
token,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
const { token: authToken, member } = response.data;
|
|
60
|
+
const redirectPath = path || '/dashboard';
|
|
61
|
+
// Update auth context
|
|
62
|
+
onTokenChange(authToken);
|
|
63
|
+
// Call onLogin callback
|
|
64
|
+
await onLogin({
|
|
65
|
+
token: authToken,
|
|
66
|
+
member,
|
|
67
|
+
});
|
|
68
|
+
// Redirect to the specified path
|
|
69
|
+
if (typeof window !== 'undefined') {
|
|
70
|
+
window.location.assign(redirectPath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const errorMessage = err?.response?.data?.errors?.[0]?.message || 'Registration failed';
|
|
75
|
+
setError(errorMessage);
|
|
76
|
+
showMessage({ message: errorMessage });
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
setIsLoading(false);
|
|
80
|
+
}
|
|
81
|
+
}, [partner, token, path, onLogin, onTokenChange]);
|
|
82
|
+
return {
|
|
83
|
+
isLoading,
|
|
84
|
+
error,
|
|
85
|
+
tokenData,
|
|
86
|
+
register,
|
|
87
|
+
checkEmailExists,
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
export { usePartnerRegistration };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -7,7 +7,6 @@ import { useAuthPackageContext } from '../../hooks/internal/useAuthPackageContex
|
|
|
7
7
|
import { CDN_IMAGES_URL } from '../../constants/cdn-url';
|
|
8
8
|
import { useConsent } from './hooks/internal/useConsent';
|
|
9
9
|
import { ScreenLayout } from "../../components/ScreenLayout";
|
|
10
|
-
import { useNavigator } from '../../hooks/useNavigator';
|
|
11
10
|
import VerifyMobile from "../VerifyMobile";
|
|
12
11
|
import VerifyEmail from "../VerifyEmail";
|
|
13
12
|
import redirectHome from '../../../assets/animation/redirect-home.json';
|
|
@@ -20,7 +19,6 @@ const UserConsent = ({ userDetails, handleBack }) => {
|
|
|
20
19
|
const [sessionToken, setSessionToken] = useState('');
|
|
21
20
|
const { onLogin, appConfig: { appName, privacyPolicyUrl }, LogoComponent, onTokenChange } = useAuthPackageContext();
|
|
22
21
|
const { loading, onAgree } = useConsent();
|
|
23
|
-
const navigator = useNavigator();
|
|
24
22
|
const onAgreeHandler = (res) => {
|
|
25
23
|
const { token, member, emailVerificationRequired, mobileVerificationRequired, sessionToken } = res;
|
|
26
24
|
if (token && member) {
|
|
@@ -58,18 +56,25 @@ const UserConsent = ({ userDetails, handleBack }) => {
|
|
|
58
56
|
source: 'web',
|
|
59
57
|
onResult: onAgreeHandler
|
|
60
58
|
}), 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 &&
|
|
61
|
-
_jsx(RedirectToHomeModal, { redirectModal: redirectModal
|
|
59
|
+
_jsx(RedirectToHomeModal, { redirectModal: redirectModal }), showVerifyMobileModal &&
|
|
62
60
|
_jsx(VerifyMobileModal, { phone: phone, visible: showVerifyMobileModal, hide: () => setShowVerifyMobileModal(false), sessionToken: sessionToken }), showVerifyEmailModal &&
|
|
63
61
|
_jsx(VerifyEmailModal, { email: email, visible: showVerifyEmailModal, hide: () => setShowVerifyEmailModal(false), sessionToken: sessionToken, onVerifiedOTP: () => {
|
|
64
62
|
setShowVerifyEmailModal(false);
|
|
65
63
|
setShowVerifyMobileModal(true);
|
|
66
64
|
} })] }));
|
|
67
65
|
};
|
|
68
|
-
const RedirectToHomeModal = ({ redirectModal
|
|
66
|
+
const RedirectToHomeModal = ({ redirectModal }) => {
|
|
67
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
69
68
|
useEffect(() => {
|
|
70
|
-
|
|
69
|
+
setIsMounted(true);
|
|
70
|
+
// Redirect after a short delay to show the modal
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
// Use window.location for full page navigation to avoid context issues
|
|
73
|
+
window.location.href = '/login';
|
|
74
|
+
}, 2000);
|
|
75
|
+
return () => clearTimeout(timer);
|
|
71
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
|
+
return (_jsxs(Modal, { title: "", open: redirectModal, showCloseButton: false, footer: null, children: [isMounted && (_jsx(Suspense, { fallback: _jsx("div", { className: "h-[150px] w-[250px]" }), children: _jsx(Lottie
|
|
73
78
|
/* @ts-ignore */
|
|
74
79
|
, {
|
|
75
80
|
/* @ts-ignore */
|
|
@@ -77,7 +82,7 @@ const RedirectToHomeModal = ({ redirectModal, navigator }) => {
|
|
|
77
82
|
animationData: redirectHome,
|
|
78
83
|
loop: true,
|
|
79
84
|
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." })] })] })] }));
|
|
85
|
+
}, height: 150, width: 250 }) })), _jsxs(Flex, { direction: 'column', style: { marginTop: isMounted ? "-30px" : "0" }, 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." })] })] })] }));
|
|
81
86
|
};
|
|
82
87
|
const VerifyMobileModal = ({ visible, hide, sessionToken, phone }) => {
|
|
83
88
|
return (_jsx(ResponsiveModal, { title: 'Verify Mobile', open: visible, onClose: hide, onOpenChange: hide, maskClosable: false, showCloseButton: false, children: _jsx(VerifyMobile, { sessionToken: sessionToken, phone: phone }) }));
|
package/build/types/index.d.ts
CHANGED
|
@@ -9,5 +9,7 @@ export * from './navigator';
|
|
|
9
9
|
export * from './screens/Login/components/LoginWebComponent/index';
|
|
10
10
|
export * from './screens/SignUp/components/SignUpWebComponent/index';
|
|
11
11
|
export * from './screens/SSOLogin/SSOCallback/components/SSOCallbackComponent/index';
|
|
12
|
+
export * from './screens/PartnerSSO/PartnerLogin/components/PartnerLoginWebComponent';
|
|
13
|
+
export * from './screens/PartnerSSO/PartnerRegistration/components/PartnerRegistrationWebComponent';
|
|
12
14
|
export * from './screens/Profile';
|
|
13
15
|
export { strongPasswordRequirements } from './constants/password-requirements';
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
interface UseCreatePasswordProps {
|
|
2
|
+
/** Initial password value (for preserving state when navigating back) */
|
|
3
|
+
initialPassword?: string;
|
|
4
|
+
/** Initial confirm password value (for preserving state when navigating back) */
|
|
5
|
+
initialConfirmPassword?: string;
|
|
6
|
+
}
|
|
1
7
|
/**
|
|
2
8
|
* @internal
|
|
3
9
|
* Internal hook for managing CreatePassword screen state and auth context integration.
|
|
4
10
|
* Not exposed to package consumers.
|
|
11
|
+
*
|
|
12
|
+
* Both web and native use the same pattern:
|
|
13
|
+
* - Hook manages password state internally
|
|
14
|
+
* - Use handlePassword/handleConfirmPassword to update state
|
|
15
|
+
* - Optional initial values for preserving state when navigating back
|
|
5
16
|
*/
|
|
6
|
-
declare const useCreatePassword: () => {
|
|
17
|
+
declare const useCreatePassword: (props?: UseCreatePasswordProps) => {
|
|
7
18
|
password: string;
|
|
8
19
|
setPassword: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
9
20
|
confirmPassword: string;
|
package/build/types/screens/PartnerSSO/PartnerLogin/components/PartnerLoginWebComponent/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface PartnerLoginWebComponentProps {
|
|
2
|
+
/**
|
|
3
|
+
* Partner key - can be passed as prop (for react-router) or extracted from query params (for Next.js)
|
|
4
|
+
*/
|
|
5
|
+
partner?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* PartnerLoginWebComponent
|
|
9
|
+
*
|
|
10
|
+
* Handles partner SSO authentication flow for web applications.
|
|
11
|
+
* Extracts token and partner from URL query params, authenticates with backend,
|
|
12
|
+
* and redirects to the specified path on success.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // In your app's partner login route (react-router - pass partner from useParams)
|
|
17
|
+
* const { partner } = useParams();
|
|
18
|
+
* <PartnerLoginWebComponent partner={partner} />
|
|
19
|
+
*
|
|
20
|
+
* // In Next.js (partner comes from query params automatically)
|
|
21
|
+
* <PartnerLoginWebComponent />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare const PartnerLoginWebComponent: ({ partner: partnerProp }: PartnerLoginWebComponentProps) => import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export { PartnerLoginWebComponent };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { UsePartnerLoginProps, UsePartnerLoginResult } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
* Hook for managing Partner SSO Login authentication.
|
|
5
|
+
* This hook is not exposed to package consumers.
|
|
6
|
+
*/
|
|
7
|
+
declare const usePartnerLogin: ({ partner, token, path }: UsePartnerLoginProps) => UsePartnerLoginResult;
|
|
8
|
+
export { usePartnerLogin };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface PartnerLoginProps {
|
|
2
|
+
/**
|
|
3
|
+
* Called after successful authentication
|
|
4
|
+
*/
|
|
5
|
+
onSuccess?: (data: PartnerLoginSuccessData) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Called when authentication fails
|
|
8
|
+
*/
|
|
9
|
+
onError?: (error: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface PartnerLoginSuccessData {
|
|
12
|
+
token: string;
|
|
13
|
+
member: {
|
|
14
|
+
memberId: string;
|
|
15
|
+
firstName: string;
|
|
16
|
+
lastName: string;
|
|
17
|
+
name: string;
|
|
18
|
+
email: string;
|
|
19
|
+
gender: string;
|
|
20
|
+
dateOfBirth: string;
|
|
21
|
+
clientId: number;
|
|
22
|
+
plan: number;
|
|
23
|
+
planExpiryDate: string | null;
|
|
24
|
+
};
|
|
25
|
+
redirectPath: string;
|
|
26
|
+
}
|
|
27
|
+
export interface UsePartnerLoginProps {
|
|
28
|
+
partner: string;
|
|
29
|
+
token: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface UsePartnerLoginResult {
|
|
33
|
+
isLoading: boolean;
|
|
34
|
+
error: string | null;
|
|
35
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
interface PartnerRegistrationWebComponentProps {
|
|
2
|
+
/**
|
|
3
|
+
* Partner key - can be passed as prop (for react-router) or extracted from query params (for Next.js)
|
|
4
|
+
*/
|
|
5
|
+
partner?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* PartnerRegistrationWebComponent
|
|
9
|
+
*
|
|
10
|
+
* Handles partner SSO registration flow for web applications.
|
|
11
|
+
* Pre-fills form with data from JWT token and collects missing information.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // In your app's partner registration route (react-router - pass partner from useParams)
|
|
16
|
+
* const { partner } = useParams();
|
|
17
|
+
* <PartnerRegistrationWebComponent partner={partner} />
|
|
18
|
+
*
|
|
19
|
+
* // In Next.js (partner comes from query params automatically)
|
|
20
|
+
* <PartnerRegistrationWebComponent />
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare const PartnerRegistrationWebComponent: ({ partner: partnerProp }: PartnerRegistrationWebComponentProps) => import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export { PartnerRegistrationWebComponent };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { UsePartnerRegistrationProps, UsePartnerRegistrationResult } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
* Hook for managing Partner SSO Registration.
|
|
5
|
+
* This hook is not exposed to package consumers.
|
|
6
|
+
*/
|
|
7
|
+
declare const usePartnerRegistration: ({ partner, token, path, }: UsePartnerRegistrationProps) => UsePartnerRegistrationResult;
|
|
8
|
+
export { usePartnerRegistration };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface PartnerRegistrationProps {
|
|
2
|
+
/**
|
|
3
|
+
* Called after successful registration
|
|
4
|
+
*/
|
|
5
|
+
onSuccess?: (data: PartnerRegistrationSuccessData) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Called when registration fails
|
|
8
|
+
*/
|
|
9
|
+
onError?: (error: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface PartnerRegistrationSuccessData {
|
|
12
|
+
token: string;
|
|
13
|
+
member: {
|
|
14
|
+
memberId: string;
|
|
15
|
+
firstName: string;
|
|
16
|
+
lastName: string;
|
|
17
|
+
name: string;
|
|
18
|
+
email: string;
|
|
19
|
+
gender: string;
|
|
20
|
+
dateOfBirth: string;
|
|
21
|
+
clientId: number;
|
|
22
|
+
plan: number;
|
|
23
|
+
planExpiryDate: string | null;
|
|
24
|
+
};
|
|
25
|
+
redirectPath: string;
|
|
26
|
+
}
|
|
27
|
+
export interface UsePartnerRegistrationProps {
|
|
28
|
+
partner: string;
|
|
29
|
+
token: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface UsePartnerRegistrationResult {
|
|
33
|
+
isLoading: boolean;
|
|
34
|
+
error: string | null;
|
|
35
|
+
tokenData: PartnerTokenData | null;
|
|
36
|
+
register: (formData: PartnerRegistrationFormData) => Promise<void>;
|
|
37
|
+
checkEmailExists: (email: string) => Promise<boolean>;
|
|
38
|
+
}
|
|
39
|
+
export interface PartnerTokenData {
|
|
40
|
+
partnerId?: number;
|
|
41
|
+
employeeId?: string;
|
|
42
|
+
firstName?: string;
|
|
43
|
+
lastName?: string;
|
|
44
|
+
gender?: 'M' | 'F';
|
|
45
|
+
dob?: string;
|
|
46
|
+
partnerMemberId?: string;
|
|
47
|
+
entityId?: string;
|
|
48
|
+
email?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface PartnerRegistrationFormData {
|
|
51
|
+
email: string;
|
|
52
|
+
countryCode: string;
|
|
53
|
+
mobile: string;
|
|
54
|
+
firstName: string;
|
|
55
|
+
lastName: string;
|
|
56
|
+
gender: 'M' | 'F';
|
|
57
|
+
dateOfBirth: string;
|
|
58
|
+
}
|
|
59
|
+
export interface Country {
|
|
60
|
+
countryCode: string;
|
|
61
|
+
phoneCode: string;
|
|
62
|
+
name: string;
|
|
63
|
+
}
|
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": "3.0.
|
|
7
|
+
"version": "3.0.3",
|
|
8
8
|
"main": "build/src/index.js",
|
|
9
9
|
"types": "build/types/index.d.ts",
|
|
10
10
|
"files": [
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"@twotalltotems/react-native-otp-input": "1.3.11",
|
|
32
32
|
"@types/crypto-js": "^4.2.2",
|
|
33
33
|
"@types/fbemitter": "^2.0.35",
|
|
34
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
34
35
|
"@types/lodash": "^4.17.15",
|
|
35
36
|
"@types/react": "^18.2.0",
|
|
36
37
|
"@types/react-google-recaptcha": "^2.1.9",
|
|
@@ -43,6 +44,8 @@
|
|
|
43
44
|
"del-cli": "^6.0.0",
|
|
44
45
|
"fbemitter": "^3.0.0",
|
|
45
46
|
"js-crypto-rsa": "^1.0.7",
|
|
47
|
+
"jsonwebtoken": "^9.0.3",
|
|
48
|
+
"jwt-decode": "^4.0.0",
|
|
46
49
|
"lottie-react": "^2.4.1",
|
|
47
50
|
"lottie-react-native": "6.7.2",
|
|
48
51
|
"moment": "^2.30.1",
|