@truworth/twc-auth 1.2.5 → 1.2.6
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/assets/email_icon.svg +6 -0
- package/build/src/constants/social-login-options.js +16 -0
- package/build/src/contexts/AuthContext.js +4 -2
- package/build/src/enums/index.js +1 -0
- package/build/src/enums/socialLogins.enum.js +6 -0
- package/build/src/helpers/{Network.js → network.js} +1 -1
- package/build/src/hooks/useRequest.js +1 -1
- package/build/src/screens/EnterEmail/hooks/internal/useEnterEmail.js +0 -1
- package/build/src/screens/EnterEmail/index.js +31 -3
- package/build/src/screens/SignUp/components/SignUpWebComponent/index.js +11 -1
- package/build/src/screens/SignUp/hooks/internal/useSignUp.js +1 -1
- package/build/src/screens/SignUp/index.native.js +3 -0
- package/build/src/screens/UserConsent/hooks/internal/useConsent.js +10 -1
- package/build/src/screens/Welcome/SocialAuth/commonSocialAuth.js +127 -0
- package/build/src/screens/Welcome/SocialAuth/hooks/useSocialAuth.native.js +192 -0
- package/build/src/screens/Welcome/SocialAuth/hooks/useSocialAuth.web.js +13 -0
- package/build/src/screens/Welcome/SocialAuth/hooks/web/useFacebookAuth.web.js +118 -0
- package/build/src/screens/Welcome/SocialAuth/hooks/web/useGoogleAuth.web.js +75 -0
- package/build/src/screens/Welcome/components/SocialLoginModal/index.js +9 -0
- package/build/src/screens/Welcome/components/SocialLoginModal/index.native.js +21 -0
- package/build/src/screens/Welcome/components/SocialLoginModal/types.js +1 -0
- package/build/src/screens/Welcome/hooks/internal/useWelcome.js +10 -5
- package/build/src/screens/Welcome/index.native.js +41 -4
- package/build/types/constants/social-login-options.d.ts +3 -0
- package/build/types/contexts/AuthContext.d.ts +2 -1
- package/build/types/contexts/type.d.ts +9 -0
- package/build/types/enums/index.d.ts +1 -0
- package/build/types/enums/socialLogins.enum.d.ts +5 -0
- package/build/types/helpers/types.d.ts +7 -1
- package/build/types/navigator/index.native.d.ts +3 -0
- package/build/types/screens/EnterEmail/hooks/internal/useEnterEmail.d.ts +0 -1
- package/build/types/screens/Welcome/SocialAuth/commonSocialAuth.d.ts +50 -0
- package/build/types/screens/Welcome/SocialAuth/hooks/useSocialAuth.native.d.ts +7 -0
- package/build/types/screens/Welcome/SocialAuth/hooks/useSocialAuth.web.d.ts +7 -0
- package/build/types/screens/Welcome/SocialAuth/hooks/web/useFacebookAuth.web.d.ts +5 -0
- package/build/types/screens/Welcome/SocialAuth/hooks/web/useGoogleAuth.web.d.ts +4 -0
- package/build/types/screens/Welcome/components/SocialLoginModal/index.d.ts +3 -0
- package/build/types/screens/Welcome/components/SocialLoginModal/index.native.d.ts +3 -0
- package/build/types/screens/Welcome/components/SocialLoginModal/types.d.ts +8 -0
- package/build/types/screens/Welcome/hooks/internal/useWelcome.d.ts +8 -0
- package/build/types/types/types.d.ts +3 -0
- package/package.json +3 -1
- package/build/assets/okay_icon copy.svg +0 -3
- /package/build/src/helpers/{Validation.js → validation.js} +0 -0
- /package/build/types/helpers/{Network.d.ts → network.d.ts} +0 -0
- /package/build/types/helpers/{Validation.d.ts → validation.d.ts} +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="18" viewBox="0 0 22 18">
|
|
2
|
+
<g fill="#ACD7D3">
|
|
3
|
+
<path d="M6.058 5.341c0 .568-.462 1.031-1.032 1.031H2.62c-.57 0-1.032-.463-1.032-1.03 0-.57.463-1.033 1.032-1.033h2.406c.57 0 1.032.463 1.032 1.032zM8.78 8.97c0 .568-.462 1.031-1.033 1.031H5.341c-.569 0-1.032-.463-1.032-1.03 0-.57.463-1.033 1.032-1.033h2.406c.57 0 1.032.463 1.032 1.032zM11.274 12.6c0 .567-.462 1.03-1.032 1.03H7.836c-.569 0-1.032-.463-1.032-1.03 0-.57.463-1.033 1.032-1.033h2.406c.57 0 1.032.463 1.032 1.032z"/>
|
|
4
|
+
<path d="M18.448.005H3.462C3.454.005 3.447 0 3.438 0H1.032C.462 0 0 .463 0 1.032 0 1.6.463 2.063 1.032 2.063h4.126l5.277 5.347c1.375 1.392 3.623 1.407 5.016.033l4.406-4.349c.048.145.08.297.08.457v10.361c0 .82-.668 1.488-1.489 1.488h-6.232c-.016 0-.03-.01-.047-.01H9.763c-.57 0-1.032.464-1.032 1.033 0 .568.463 1.03 1.032 1.03h.595v.006h8.09c1.956 0 3.547-1.59 3.547-3.547V3.552c0-1.957-1.59-3.547-3.547-3.547zm-4.443 5.973c-.584.576-1.529.57-2.105-.014L8.05 2.063h9.92l-3.965 3.915z"/>
|
|
5
|
+
</g>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SocialLoginsEnum } from "../enums";
|
|
2
|
+
const socialLoginOptions = [
|
|
3
|
+
{
|
|
4
|
+
type: SocialLoginsEnum.Google,
|
|
5
|
+
iconImageUrl: `https://cdn-thewellnesscorner.s3.amazonaws.com/twc-web-images/template/google-icon.png`,
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
type: SocialLoginsEnum.Facebook,
|
|
9
|
+
iconImageUrl: `https://cdn-thewellnesscorner.s3.amazonaws.com/twc-web-images/template/facebook-icon.png`,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
type: SocialLoginsEnum.Apple,
|
|
13
|
+
iconImageUrl: `https://cdn-thewellnesscorner.s3.amazonaws.com/twc-web-images/template/apple-icon.png`,
|
|
14
|
+
}
|
|
15
|
+
];
|
|
16
|
+
export { socialLoginOptions };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React, { useEffect, useState, useMemo, useCallback } from 'react';
|
|
3
3
|
import { MEMBER_IMAGES_URL } from '../constants/cdn-url';
|
|
4
|
-
import { createHttpClient } from '../helpers/
|
|
4
|
+
import { createHttpClient } from '../helpers/network';
|
|
5
5
|
/**
|
|
6
6
|
* Public Auth Authentication Context
|
|
7
7
|
*
|
|
@@ -32,8 +32,9 @@ const AuthPackageContext = React.createContext(null);
|
|
|
32
32
|
* @param onLaunchAuthSession - Optional callback after successfully launching the auth session. For example, it can be used to set the Firebase notification token or request Android permissions.
|
|
33
33
|
* @param onRefreshSession - Optional handler for refreshing user session
|
|
34
34
|
* @param openChatSupport - Optional callback to open chat support
|
|
35
|
+
* @param socialLoginConfig - Optional social login configuration
|
|
35
36
|
*/
|
|
36
|
-
const AuthProvider = ({ children, LogoComponent, session, appConfig, onLogin, onLogout, onLaunchAuthSession, onRefreshSession, openChatSupport }) => {
|
|
37
|
+
const AuthProvider = ({ children, LogoComponent, session, appConfig, onLogin, onLogout, onLaunchAuthSession, onRefreshSession, openChatSupport, socialLoginConfig }) => {
|
|
37
38
|
const [isLoadingProfile, setIsLoadingProfile] = useState(false);
|
|
38
39
|
const [profile, setProfile] = useState(null);
|
|
39
40
|
const [client, setClient] = useState(null);
|
|
@@ -171,6 +172,7 @@ const AuthProvider = ({ children, LogoComponent, session, appConfig, onLogin, on
|
|
|
171
172
|
LogoComponent,
|
|
172
173
|
appConfig: appConfigValue,
|
|
173
174
|
registrationMethod,
|
|
175
|
+
socialLoginConfig,
|
|
174
176
|
onLaunchAuthSession,
|
|
175
177
|
onLogin,
|
|
176
178
|
logout,
|
package/build/src/enums/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { axiosClient } from '../api/axiosClient';
|
|
2
|
-
import { showMessage } from '
|
|
2
|
+
import { showMessage } from './show-message';
|
|
3
3
|
import {} from 'axios';
|
|
4
4
|
export const createHttpClient = ({ token, onRefreshSession, onLogout }) => {
|
|
5
5
|
let currentToken = token;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { showMessage } from '../helpers/show-message';
|
|
3
3
|
import { useAuthPackageContext } from './internal/useAuthPackageContext';
|
|
4
|
-
import { createHttpClient } from '../helpers/
|
|
4
|
+
import { createHttpClient } from '../helpers/network';
|
|
5
5
|
const useRequest = () => {
|
|
6
6
|
const { token: sessionToken, logout, refreshSession } = useAuthPackageContext();
|
|
7
7
|
const [token, setToken] = useState(sessionToken);
|
|
@@ -69,7 +69,6 @@ const useEnterEmail = () => {
|
|
|
69
69
|
handleClearEmail,
|
|
70
70
|
isLoginConflictModalVisible,
|
|
71
71
|
setIsLoginConflictModalVisible,
|
|
72
|
-
onRegistrationMethodChange,
|
|
73
72
|
appName: appConfig.appName,
|
|
74
73
|
loginConflictTitle: `Sign in with ${loginType}`,
|
|
75
74
|
loginConflictDescription: `Looks like you previously registered using ${loginType}. Please go back and sign in with ${loginType}.`,
|
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Button, Form, TextInput, useForm } from '@truworth/twc-web-design';
|
|
2
|
+
import { Button, Flex, Form, TextInput, Typography, useForm } from '@truworth/twc-web-design';
|
|
3
3
|
import { ConfirmationModal as LoginConflictModal } from '../../components/ConfirmationModal';
|
|
4
4
|
import { ScreenLayout } from "../../components/ScreenLayout";
|
|
5
5
|
import { useEnterEmail } from "./hooks/internal/useEnterEmail";
|
|
6
6
|
import { SupportDetails } from '../../components/SupportDetails';
|
|
7
|
+
import { useWelcome } from '../Welcome/hooks/internal/useWelcome';
|
|
8
|
+
import { socialLoginOptions } from '../../constants/social-login-options';
|
|
9
|
+
import { SocialLoginModal } from '../Welcome/components/SocialLoginModal';
|
|
10
|
+
import { useSocialAuth } from '../Welcome/SocialAuth/hooks/useSocialAuth.web';
|
|
11
|
+
import { SocialLoginsEnum } from '../../enums';
|
|
12
|
+
import _ from 'lodash';
|
|
7
13
|
const EnterEmail = ({ onContinue, onPressSignInWithSSO }) => {
|
|
8
|
-
const {
|
|
14
|
+
const { loginWithGoogle, loginWithFacebook } = useSocialAuth();
|
|
15
|
+
const { email, isEmailValid, handleEmailChange, appName, handleEmailExists, loading, loginConflictTitle, loginConflictDescription, isLoginConflictModalVisible, setIsLoginConflictModalVisible, } = useEnterEmail();
|
|
16
|
+
const { showSocialLoginModal, setShowSocialLoginModal, socialLoginType, setSocialLoginType, isSocialLoginEnabled, socialLoginConfig, } = useWelcome();
|
|
9
17
|
const form = useForm({ defaultValues: { email } });
|
|
18
|
+
const handleLogin = () => {
|
|
19
|
+
setShowSocialLoginModal(false);
|
|
20
|
+
switch (socialLoginType) {
|
|
21
|
+
case SocialLoginsEnum.Google:
|
|
22
|
+
return loginWithGoogle();
|
|
23
|
+
case SocialLoginsEnum.Facebook:
|
|
24
|
+
return loginWithFacebook();
|
|
25
|
+
default:
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
10
29
|
return (_jsxs(_Fragment, { children: [_jsxs(ScreenLayout, { title: `Hi, Welcome To ${appName}!`, subTitle: "Please enter email to get started.", buttonProps: {
|
|
11
30
|
loading,
|
|
12
31
|
label: 'Continue',
|
|
@@ -21,6 +40,15 @@ const EnterEmail = ({ onContinue, onPressSignInWithSSO }) => {
|
|
|
21
40
|
handleEmailChange(e.target.value);
|
|
22
41
|
}
|
|
23
42
|
}) }) }) }), isLoginConflictModalVisible &&
|
|
24
|
-
_jsx(LoginConflictModal, { title: loginConflictTitle, description: loginConflictDescription, visible: isLoginConflictModalVisible, onProceed: () => setIsLoginConflictModalVisible(false), onClose: () => setIsLoginConflictModalVisible(false), primaryLabel: 'Okay' })] }), _jsx(Button, { isFullWidth: true, type: "button", label: 'Sign In with SSO', variant: 'secondary', className: 'flex-none mt-6', onClick: onPressSignInWithSSO }), _jsx(
|
|
43
|
+
_jsx(LoginConflictModal, { title: loginConflictTitle, description: loginConflictDescription, visible: isLoginConflictModalVisible, onProceed: () => setIsLoginConflictModalVisible(false), onClose: () => setIsLoginConflictModalVisible(false), primaryLabel: 'Okay' })] }), _jsx(Button, { isFullWidth: true, type: "button", label: 'Sign In with SSO', variant: 'secondary', className: 'flex-none mt-6', onClick: onPressSignInWithSSO }), isSocialLoginEnabled && (_jsxs(_Fragment, { children: [_jsxs(Flex, { align: "center", className: "my-6", children: [_jsx(Flex, { className: "flex-1 border-t border-gray-300" }), _jsx(Typography, { type: 'body', size: 'small', className: "text-gray-500 px-4", children: "or continue with" }), _jsx(Flex, { className: "flex-1 border-t border-gray-300" })] }), _jsx(Flex, { justify: 'center', align: 'center', className: "gap-4", children: socialLoginOptions.map(({ type, iconImageUrl }) => {
|
|
44
|
+
if (type === SocialLoginsEnum.Apple) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return socialLoginConfig?.[type]?.enabled && (_jsxs("button", { type: "button", className: "flex justify-center p-3 gap-2 items-center\n rounded-[50px] flex-1 border text-gray-800 hover:text-gray-800\n border-gray-100 bg-gray-100 hover:border-gray-300 transition-all duration-300 cursor-pointer", onClick: () => {
|
|
48
|
+
setSocialLoginType(type);
|
|
49
|
+
setShowSocialLoginModal(true);
|
|
50
|
+
}, children: [_jsx("img", { src: iconImageUrl, style: { width: 20, height: 20 } }), _jsx(Typography, { type: 'utility', size: 'medium', children: _.startCase(type) })] }, type));
|
|
51
|
+
}) })] })), showSocialLoginModal &&
|
|
52
|
+
_jsx(SocialLoginModal, { visible: showSocialLoginModal, hide: () => setShowSocialLoginModal(false), socialLoginType: socialLoginType, onClick: handleLogin }), _jsx(SupportDetails, {})] }));
|
|
25
53
|
};
|
|
26
54
|
export default EnterEmail;
|
|
@@ -11,18 +11,28 @@ const SignUpWebComponent = () => {
|
|
|
11
11
|
const [signUpStep, setSignUpStep] = useState('registration');
|
|
12
12
|
const [userDetails, setUserDetails] = useState({});
|
|
13
13
|
const [registrationToken, setRegistrationToken] = useState('');
|
|
14
|
+
const [fbUserId, setFbUserId] = useState('');
|
|
15
|
+
const [googleUserId, setGoogleUserId] = useState('');
|
|
14
16
|
useEffect(() => {
|
|
15
17
|
const rawRegistrationToken = router.query.registrationToken;
|
|
18
|
+
const rawFbUserId = router.query.fbUserId;
|
|
19
|
+
const rawGoogleUserId = router.query.googleUserId;
|
|
16
20
|
if (typeof rawRegistrationToken === 'string') {
|
|
17
21
|
setRegistrationToken(rawRegistrationToken);
|
|
18
22
|
}
|
|
23
|
+
if (typeof rawFbUserId === 'string') {
|
|
24
|
+
setFbUserId(rawFbUserId);
|
|
25
|
+
}
|
|
26
|
+
if (typeof rawGoogleUserId === 'string') {
|
|
27
|
+
setGoogleUserId(rawGoogleUserId);
|
|
28
|
+
}
|
|
19
29
|
}, [router.query.registrationToken]);
|
|
20
30
|
const renderStep = () => {
|
|
21
31
|
switch (signUpStep) {
|
|
22
32
|
case 'registration':
|
|
23
33
|
return (_jsx(SignUpFormComponent, { userDetails: userDetails, onContinue: (userDetails) => {
|
|
24
34
|
if (registrationToken) {
|
|
25
|
-
setUserDetails({ ...userDetails, registrationToken });
|
|
35
|
+
setUserDetails({ ...userDetails, registrationToken, fbUserId, googleUserId });
|
|
26
36
|
setSignUpStep('user-consent');
|
|
27
37
|
return;
|
|
28
38
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
|
-
import { validateEmojiRegex } from "../../../../helpers/
|
|
2
|
+
import { validateEmojiRegex } from "../../../../helpers/validation";
|
|
3
3
|
import { showMessage } from "../../../../helpers/show-message";
|
|
4
4
|
import { useAuthPackageContext } from "../../../../hooks/internal/useAuthPackageContext";
|
|
5
5
|
import { RegistrationMethod } from "../../../../enums";
|
|
@@ -50,6 +50,9 @@ const SignUp = ({ navigation, route }) => {
|
|
|
50
50
|
selectedDOB,
|
|
51
51
|
countryCode,
|
|
52
52
|
referralCode,
|
|
53
|
+
googleUserId: route.params?.googleUserId,
|
|
54
|
+
fbUserId: route.params?.fbUserId,
|
|
55
|
+
appleUserId: route.params?.appleUserId
|
|
53
56
|
};
|
|
54
57
|
if (route.params?.registrationToken) {
|
|
55
58
|
navigation.navigate('UserConsent', {
|
|
@@ -10,7 +10,7 @@ const useConsent = () => {
|
|
|
10
10
|
const [loading, setLoading] = useState(false);
|
|
11
11
|
const { registrationMethod } = useAuthPackageContext();
|
|
12
12
|
const onAgree = ({ userDetails, source, onResult }) => {
|
|
13
|
-
const { firstName, lastName, selectedDOB, gender, referralCode, email, phone, countryCode, password, registrationToken } = userDetails || {};
|
|
13
|
+
const { firstName, lastName, selectedDOB, gender, referralCode, email, phone, countryCode, password, registrationToken, fbUserId, googleUserId, appleUserId } = userDetails || {};
|
|
14
14
|
const payload = {
|
|
15
15
|
firstName,
|
|
16
16
|
lastName,
|
|
@@ -37,6 +37,15 @@ const useConsent = () => {
|
|
|
37
37
|
if (registrationToken) {
|
|
38
38
|
payload.ssoRegistrationToken = registrationToken;
|
|
39
39
|
}
|
|
40
|
+
if (googleUserId) {
|
|
41
|
+
payload.googleUserId = googleUserId;
|
|
42
|
+
}
|
|
43
|
+
if (fbUserId) {
|
|
44
|
+
payload.fbUserId = fbUserId;
|
|
45
|
+
}
|
|
46
|
+
if (appleUserId) {
|
|
47
|
+
payload.appleUserId = appleUserId;
|
|
48
|
+
}
|
|
40
49
|
setLoading(true);
|
|
41
50
|
axiosClient({
|
|
42
51
|
url: '/auth/registration/initiate',
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// src/screens/Welcome/SocialAuth/commonSocialAuth.ts
|
|
2
|
+
import { LoginMethodCode, SocialLoginsEnum } from "../../../enums";
|
|
3
|
+
import { axiosClient } from "../../../api/axiosClient";
|
|
4
|
+
import { checkEmailExists } from "../../../api/auth";
|
|
5
|
+
// ============================================
|
|
6
|
+
// TYPES & ENUMS
|
|
7
|
+
// ============================================
|
|
8
|
+
export var SocialAuthStatus;
|
|
9
|
+
(function (SocialAuthStatus) {
|
|
10
|
+
SocialAuthStatus["REGISTER"] = "REGISTER";
|
|
11
|
+
SocialAuthStatus["INVALID"] = "INVALID";
|
|
12
|
+
SocialAuthStatus["SUCCESS"] = "SUCCESS";
|
|
13
|
+
})(SocialAuthStatus || (SocialAuthStatus = {}));
|
|
14
|
+
// ============================================
|
|
15
|
+
// CONSTANTS
|
|
16
|
+
// ============================================
|
|
17
|
+
const EXISTING_LOGIN_TYPE = {
|
|
18
|
+
[LoginMethodCode.Email]: 'Email Address',
|
|
19
|
+
[LoginMethodCode.Facebook]: 'Facebook Account',
|
|
20
|
+
[LoginMethodCode.Google]: 'Google Account',
|
|
21
|
+
[LoginMethodCode.Apple]: 'Apple ID',
|
|
22
|
+
};
|
|
23
|
+
const PROVIDER_CONFIG = {
|
|
24
|
+
[SocialLoginsEnum.Google]: {
|
|
25
|
+
endpoint: '/auth/google',
|
|
26
|
+
tokenKey: 'token',
|
|
27
|
+
loginMethodCode: LoginMethodCode.Google,
|
|
28
|
+
},
|
|
29
|
+
[SocialLoginsEnum.Facebook]: {
|
|
30
|
+
endpoint: '/auth/facebook',
|
|
31
|
+
tokenKey: 'token',
|
|
32
|
+
loginMethodCode: LoginMethodCode.Facebook,
|
|
33
|
+
},
|
|
34
|
+
[SocialLoginsEnum.Apple]: {
|
|
35
|
+
endpoint: '/auth/apple/appleid-exists',
|
|
36
|
+
tokenKey: 'token',
|
|
37
|
+
loginMethodCode: LoginMethodCode.Apple,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
// ============================================
|
|
41
|
+
// HELPER FUNCTIONS
|
|
42
|
+
// ============================================
|
|
43
|
+
/**
|
|
44
|
+
* Verifies if the attempted login method matches the registered method for an email
|
|
45
|
+
* @param email - User's email address
|
|
46
|
+
* @param attemptedLoginCode - Login method code being attempted
|
|
47
|
+
* @returns Verification result with email existence and validity
|
|
48
|
+
*/
|
|
49
|
+
const verifyLoginType = async (email, attemptedLoginCode) => {
|
|
50
|
+
const { emailExist, loginType } = await checkEmailExists(email);
|
|
51
|
+
const existingLoginCode = Number(loginType);
|
|
52
|
+
if (emailExist) {
|
|
53
|
+
const isValidLoginType = EXISTING_LOGIN_TYPE[attemptedLoginCode] === EXISTING_LOGIN_TYPE[existingLoginCode];
|
|
54
|
+
return {
|
|
55
|
+
emailExist,
|
|
56
|
+
isValidLoginType,
|
|
57
|
+
existingLoginType: EXISTING_LOGIN_TYPE[existingLoginCode],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { emailExist };
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Generic handler for social authentication
|
|
64
|
+
* @param provider - Social provider (google, facebook, apple)
|
|
65
|
+
* @param email - User's email
|
|
66
|
+
* @param identityToken - Provider's identity token
|
|
67
|
+
* @param platform - Platform (web, ios, android)
|
|
68
|
+
* @returns Authentication result
|
|
69
|
+
*/
|
|
70
|
+
const handleSocialAuth = async (provider, email, identityToken, platform) => {
|
|
71
|
+
try {
|
|
72
|
+
const config = PROVIDER_CONFIG[provider];
|
|
73
|
+
// Verify login type
|
|
74
|
+
const { emailExist, isValidLoginType, existingLoginType } = await verifyLoginType(email, config.loginMethodCode);
|
|
75
|
+
// New user - needs registration
|
|
76
|
+
if (!emailExist) {
|
|
77
|
+
return {
|
|
78
|
+
status: SocialAuthStatus.REGISTER,
|
|
79
|
+
data: { email },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Wrong Social login method
|
|
83
|
+
if (!isValidLoginType) {
|
|
84
|
+
return {
|
|
85
|
+
status: SocialAuthStatus.INVALID,
|
|
86
|
+
data: { existingLoginType },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Authenticate with backend
|
|
90
|
+
const { data } = await axiosClient.post(config.endpoint, {
|
|
91
|
+
[config.tokenKey]: identityToken,
|
|
92
|
+
source: platform,
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
status: SocialAuthStatus.SUCCESS,
|
|
96
|
+
data: {
|
|
97
|
+
token: data.token,
|
|
98
|
+
member: data.member,
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
// Let the calling code handle the error
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
// ============================================
|
|
108
|
+
// PUBLIC API
|
|
109
|
+
// ============================================
|
|
110
|
+
/**
|
|
111
|
+
* Handles Google authentication
|
|
112
|
+
*/
|
|
113
|
+
export const handleGoogleAuth = async ({ email, googleIdentityToken, platform, }) => {
|
|
114
|
+
return handleSocialAuth(SocialLoginsEnum.Google, email, googleIdentityToken, platform);
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Handles Facebook authentication
|
|
118
|
+
*/
|
|
119
|
+
export const handleFacebookAuth = async ({ email, facebookIdentityToken, platform }) => {
|
|
120
|
+
return handleSocialAuth(SocialLoginsEnum.Facebook, email, facebookIdentityToken, platform);
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Handles Apple authentication
|
|
124
|
+
*/
|
|
125
|
+
export const handleAppleAuth = async ({ email, appleIdentityToken, platform }) => {
|
|
126
|
+
return handleSocialAuth(SocialLoginsEnum.Apple, email, appleIdentityToken, platform);
|
|
127
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { Alert, Linking, Platform } from "react-native";
|
|
3
|
+
import { useAuthPackageContext } from "../../../../hooks/internal/useAuthPackageContext";
|
|
4
|
+
import { useNavigation } from "@react-navigation/native";
|
|
5
|
+
import { GoogleSignin } from "@react-native-google-signin/google-signin";
|
|
6
|
+
import { handleAppleAuth, handleFacebookAuth, handleGoogleAuth, SocialAuthStatus } from "../commonSocialAuth";
|
|
7
|
+
import { RegistrationMethod, SocialLoginsEnum } from "../../../../enums";
|
|
8
|
+
import { showMessage } from "../../../../helpers/show-message";
|
|
9
|
+
import { AccessToken, GraphRequest, GraphRequestManager, LoginManager } from "react-native-fbsdk-next";
|
|
10
|
+
import AppleAuth, { AppleAuthRequestOperation, AppleAuthRequestScope, AppleAuthCredentialState } from '@invertase/react-native-apple-authentication';
|
|
11
|
+
import { axiosClient } from "../../../../api/axiosClient";
|
|
12
|
+
const useSocialAuth = () => {
|
|
13
|
+
const [loading, setLoading] = useState(false);
|
|
14
|
+
const { onLogin, socialLoginConfig, onRegistrationMethodChange } = useAuthPackageContext();
|
|
15
|
+
const navigation = useNavigation();
|
|
16
|
+
const googleAppId = socialLoginConfig?.google?.webClientId;
|
|
17
|
+
const loginWithGoogle = useCallback(async () => {
|
|
18
|
+
try {
|
|
19
|
+
setLoading(true);
|
|
20
|
+
GoogleSignin.configure({ webClientId: googleAppId });
|
|
21
|
+
await GoogleSignin.hasPlayServices();
|
|
22
|
+
const googleResponse = await GoogleSignin.signIn();
|
|
23
|
+
const { user, idToken } = googleResponse.data || {};
|
|
24
|
+
if (!user || !idToken) {
|
|
25
|
+
return showMessage({
|
|
26
|
+
message: "Google login failed",
|
|
27
|
+
type: "error"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const result = await handleGoogleAuth({
|
|
31
|
+
email: user.email,
|
|
32
|
+
googleIdentityToken: idToken,
|
|
33
|
+
platform: Platform.OS === "ios" ? "ios" : "android",
|
|
34
|
+
});
|
|
35
|
+
return handleSocialAuthResult(result, {
|
|
36
|
+
email: user.email,
|
|
37
|
+
firstName: user.givenName ?? '',
|
|
38
|
+
lastName: user.familyName ?? '',
|
|
39
|
+
googleUserId: user.id,
|
|
40
|
+
}, SocialLoginsEnum.Google);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.error("Google login failed:", err);
|
|
44
|
+
return { error: err };
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
}
|
|
49
|
+
}, [googleAppId]);
|
|
50
|
+
const loginWithFacebook = useCallback(async () => {
|
|
51
|
+
try {
|
|
52
|
+
const facebookResponse = await LoginManager.logInWithPermissions(['public_profile', 'email']);
|
|
53
|
+
if (facebookResponse.isCancelled) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setLoading(true);
|
|
57
|
+
const fbAccessToken = await AccessToken.getCurrentAccessToken();
|
|
58
|
+
const profile = await new Promise((resolve, reject) => {
|
|
59
|
+
new GraphRequestManager().addRequest(new GraphRequest('/me', {
|
|
60
|
+
accessToken: fbAccessToken?.accessToken,
|
|
61
|
+
parameters: { fields: { string: 'email,name' } },
|
|
62
|
+
}, (err, res) => (err ? reject(err) : resolve(res)))).start();
|
|
63
|
+
});
|
|
64
|
+
if (!profile.email || !profile.id) {
|
|
65
|
+
return showMessage({
|
|
66
|
+
message: "Facebook login failed",
|
|
67
|
+
type: "error"
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const nameParts = profile.name?.split(' ') || [];
|
|
71
|
+
const firstName = nameParts[0] || undefined;
|
|
72
|
+
const lastName = nameParts.slice(1).join(' ') || undefined;
|
|
73
|
+
const result = await handleFacebookAuth({
|
|
74
|
+
email: profile.email,
|
|
75
|
+
facebookIdentityToken: fbAccessToken?.accessToken ?? '',
|
|
76
|
+
platform: Platform.OS === "ios" ? "ios" : "android",
|
|
77
|
+
});
|
|
78
|
+
return handleSocialAuthResult(result, {
|
|
79
|
+
email: profile.email,
|
|
80
|
+
firstName,
|
|
81
|
+
lastName,
|
|
82
|
+
fbUserId: profile.id
|
|
83
|
+
}, SocialLoginsEnum.Facebook);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error("Facebook login failed:", err);
|
|
87
|
+
return { error: err };
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
setLoading(false);
|
|
91
|
+
}
|
|
92
|
+
}, []);
|
|
93
|
+
const loginWithApple = useCallback(async () => {
|
|
94
|
+
try {
|
|
95
|
+
const appleAuthResponse = await AppleAuth.performRequest({
|
|
96
|
+
requestedOperation: AppleAuthRequestOperation.LOGIN,
|
|
97
|
+
requestedScopes: [AppleAuthRequestScope.EMAIL,
|
|
98
|
+
AppleAuthRequestScope.FULL_NAME],
|
|
99
|
+
});
|
|
100
|
+
const credentialState = await AppleAuth.getCredentialStateForUser(appleAuthResponse.user);
|
|
101
|
+
if (credentialState !== AppleAuthCredentialState.AUTHORIZED) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
setLoading(true);
|
|
105
|
+
if (!appleAuthResponse.email) {
|
|
106
|
+
try {
|
|
107
|
+
const { data } = await axiosClient.post('/auth/apple', {
|
|
108
|
+
token: appleAuthResponse.identityToken,
|
|
109
|
+
source: Platform.OS,
|
|
110
|
+
});
|
|
111
|
+
onLogin({
|
|
112
|
+
token: data.token,
|
|
113
|
+
member: data.member
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return Alert.alert('Account Not Found!', "The Wellness Corner account linked with this Apple ID was not found. Please unlink this Apple ID from The Wellness Corner app and try again. To stop using this Apple ID with The Wellness Corner, tap on 'Know More' below.", [
|
|
119
|
+
{
|
|
120
|
+
text: 'Know More',
|
|
121
|
+
style: 'default',
|
|
122
|
+
onPress: () => Linking.openURL('https://support.apple.com/en-in/HT210426')
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
text: 'Cancel',
|
|
126
|
+
style: 'cancel'
|
|
127
|
+
}
|
|
128
|
+
], { cancelable: true });
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
setLoading(false);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!appleAuthResponse.identityToken || !appleAuthResponse.email) {
|
|
135
|
+
return showMessage({
|
|
136
|
+
message: "Apple login failed",
|
|
137
|
+
type: "error"
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const result = await handleAppleAuth({
|
|
141
|
+
email: appleAuthResponse.email,
|
|
142
|
+
appleIdentityToken: appleAuthResponse.identityToken,
|
|
143
|
+
platform: Platform.OS === "ios" ? "ios" : "android",
|
|
144
|
+
});
|
|
145
|
+
return handleSocialAuthResult(result, {
|
|
146
|
+
email: appleAuthResponse.email,
|
|
147
|
+
firstName: appleAuthResponse.fullName?.givenName ?? '',
|
|
148
|
+
lastName: appleAuthResponse.fullName?.familyName ?? '',
|
|
149
|
+
appleUserId: appleAuthResponse.user
|
|
150
|
+
}, SocialLoginsEnum.Apple);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error("Apple login failed:", error);
|
|
154
|
+
return { error: error };
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
setLoading(false);
|
|
158
|
+
}
|
|
159
|
+
}, []);
|
|
160
|
+
const providerCleanup = {
|
|
161
|
+
google: () => GoogleSignin.signOut(),
|
|
162
|
+
facebook: () => LoginManager.logOut(),
|
|
163
|
+
apple: () => Promise.resolve(), // Apple doesn't need explicit cleanup
|
|
164
|
+
};
|
|
165
|
+
const handleSocialAuthResult = async (result, userData, providerName) => {
|
|
166
|
+
switch (result.status) {
|
|
167
|
+
case SocialAuthStatus.REGISTER:
|
|
168
|
+
onRegistrationMethodChange(RegistrationMethod.SOCIAL);
|
|
169
|
+
return navigation.navigate('SignUp', userData);
|
|
170
|
+
case SocialAuthStatus.INVALID:
|
|
171
|
+
// Clean up provider session
|
|
172
|
+
await providerCleanup[providerName]();
|
|
173
|
+
return showMessage({
|
|
174
|
+
message: `You have previously registered using your ${result.data.existingLoginType}. Please use your ${result.data.existingLoginType} to login.`,
|
|
175
|
+
type: 'error'
|
|
176
|
+
});
|
|
177
|
+
case SocialAuthStatus.SUCCESS:
|
|
178
|
+
onLogin({
|
|
179
|
+
token: result.data.token,
|
|
180
|
+
member: result.data.member
|
|
181
|
+
});
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
loading,
|
|
187
|
+
loginWithGoogle,
|
|
188
|
+
loginWithFacebook,
|
|
189
|
+
loginWithApple
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
export { useSocialAuth };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useFacebookAuth } from "./web/useFacebookAuth.web";
|
|
2
|
+
import { useGoogleAuth } from "./web/useGoogleAuth.web";
|
|
3
|
+
const useSocialAuth = () => {
|
|
4
|
+
const { loginWithGoogle, googleSignOut } = useGoogleAuth();
|
|
5
|
+
const { loginWithFacebook, facebookSignOut } = useFacebookAuth();
|
|
6
|
+
return {
|
|
7
|
+
loginWithGoogle,
|
|
8
|
+
googleSignOut,
|
|
9
|
+
loginWithFacebook,
|
|
10
|
+
facebookSignOut
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export { useSocialAuth };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { useEffect, useCallback, useState } from "react";
|
|
2
|
+
import { useRouter } from "next/router";
|
|
3
|
+
import { useAuthPackageContext } from "../../../../../hooks/internal/useAuthPackageContext";
|
|
4
|
+
import { RegistrationMethod } from "../../../../../enums";
|
|
5
|
+
import { handleFacebookAuth } from "../../commonSocialAuth";
|
|
6
|
+
import { showMessage } from "../../../../../helpers/show-message";
|
|
7
|
+
export const useFacebookAuth = () => {
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
const { onLogin, socialLoginConfig, onRegistrationMethodChange } = useAuthPackageContext();
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const facebookAppId = socialLoginConfig?.facebook?.webClientId;
|
|
12
|
+
// Load Facebook SDK once
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!facebookAppId) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
(function (d, s, id) {
|
|
18
|
+
var js, fjs = d.getElementsByTagName(s)[0];
|
|
19
|
+
if (d.getElementById(id)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
js = d.createElement(s);
|
|
23
|
+
js.id = id;
|
|
24
|
+
js.src = "https://connect.facebook.net/en_US/sdk.js";
|
|
25
|
+
if (fjs && fjs.parentNode) {
|
|
26
|
+
fjs.parentNode.insertBefore(js, fjs);
|
|
27
|
+
}
|
|
28
|
+
}(document, 'script', 'facebook-jssdk'));
|
|
29
|
+
initFacebookSdk();
|
|
30
|
+
}, [facebookAppId]);
|
|
31
|
+
const initFacebookSdk = () => {
|
|
32
|
+
window.fbAsyncInit = () => {
|
|
33
|
+
window.FB.init({
|
|
34
|
+
appId: facebookAppId,
|
|
35
|
+
cookie: true,
|
|
36
|
+
xfbml: true,
|
|
37
|
+
version: 'v16.0'
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const loginWithFacebook = () => {
|
|
42
|
+
if (!window.FB) {
|
|
43
|
+
showMessage({
|
|
44
|
+
message: "Facebook login is still initialising. Please try again in a moment.",
|
|
45
|
+
type: "warning",
|
|
46
|
+
});
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
window.FB.login(() => checkLoginState(), {
|
|
50
|
+
scope: "public_profile,email",
|
|
51
|
+
return_scopes: true,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
const checkLoginState = () => window.FB.getLoginStatus((response) => {
|
|
55
|
+
statusChangeCallback(response);
|
|
56
|
+
});
|
|
57
|
+
// ✅ Handle status
|
|
58
|
+
const statusChangeCallback = useCallback(async (response) => {
|
|
59
|
+
try {
|
|
60
|
+
if (response.status === "connected") {
|
|
61
|
+
const accessToken = response.authResponse.accessToken;
|
|
62
|
+
window.FB.api("/me", { fields: "name,email" }, async (userResponse) => {
|
|
63
|
+
const { email, name, id: fbUserId } = userResponse;
|
|
64
|
+
if (!email) {
|
|
65
|
+
facebookSignOut();
|
|
66
|
+
showMessage({
|
|
67
|
+
message: "We couldn’t retrieve your Facebook email. Please use another login method or update your Facebook permissions.",
|
|
68
|
+
type: "error",
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const nameParts = name.split(" ");
|
|
73
|
+
//{ error, isValid, loginType } = await verifyLoginType(email, LoginMethodCode.Facebook);
|
|
74
|
+
const result = await handleFacebookAuth({
|
|
75
|
+
email,
|
|
76
|
+
facebookIdentityToken: accessToken,
|
|
77
|
+
platform: "web",
|
|
78
|
+
});
|
|
79
|
+
switch (result.status) {
|
|
80
|
+
case "REGISTER":
|
|
81
|
+
onRegistrationMethodChange(RegistrationMethod.SOCIAL);
|
|
82
|
+
return router.push({
|
|
83
|
+
pathname: "/registration",
|
|
84
|
+
query: {
|
|
85
|
+
email,
|
|
86
|
+
firstName: nameParts[0],
|
|
87
|
+
lastName: nameParts[1] || "",
|
|
88
|
+
fbUserId,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
case "INVALID":
|
|
92
|
+
facebookSignOut();
|
|
93
|
+
return showMessage({
|
|
94
|
+
message: `You have previously registered using your ${result.data.existingLoginType}. Please use your ${result.data.existingLoginType} to login method.`,
|
|
95
|
+
type: "error",
|
|
96
|
+
});
|
|
97
|
+
case "SUCCESS":
|
|
98
|
+
onLogin({
|
|
99
|
+
token: result.data.token,
|
|
100
|
+
member: result.data.member
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error("Google login failed:", error);
|
|
109
|
+
setError(error);
|
|
110
|
+
return { error };
|
|
111
|
+
}
|
|
112
|
+
}, []);
|
|
113
|
+
// ✅ Logout
|
|
114
|
+
const facebookSignOut = useCallback(() => {
|
|
115
|
+
window.FB.api("/me/permissions", "delete", null, () => window.FB.logout((res) => statusChangeCallback(res)));
|
|
116
|
+
}, [statusChangeCallback]);
|
|
117
|
+
return { loginWithFacebook, facebookSignOut, error };
|
|
118
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useGoogleLogin } from "react-google-login";
|
|
3
|
+
import { useAuthPackageContext } from "../../../../../hooks/internal/useAuthPackageContext";
|
|
4
|
+
import { showMessage } from "../../../../../helpers/show-message";
|
|
5
|
+
import { RegistrationMethod } from "../../../../../enums";
|
|
6
|
+
import { useRouter } from "next/router";
|
|
7
|
+
import { handleGoogleAuth } from "../../commonSocialAuth";
|
|
8
|
+
export const useGoogleAuth = () => {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
const { onLogin, socialLoginConfig, onRegistrationMethodChange } = useAuthPackageContext();
|
|
11
|
+
const googleAppId = socialLoginConfig?.google?.webClientId;
|
|
12
|
+
const onSuccess = async (response) => {
|
|
13
|
+
const { tokenId, profileObj, googleId } = response;
|
|
14
|
+
const { email, givenName, familyName } = profileObj;
|
|
15
|
+
const result = await handleGoogleAuth({
|
|
16
|
+
email,
|
|
17
|
+
googleIdentityToken: tokenId,
|
|
18
|
+
platform: "web",
|
|
19
|
+
});
|
|
20
|
+
switch (result.status) {
|
|
21
|
+
case "REGISTER":
|
|
22
|
+
onRegistrationMethodChange(RegistrationMethod.SOCIAL);
|
|
23
|
+
return router.push({
|
|
24
|
+
pathname: "/registration",
|
|
25
|
+
query: {
|
|
26
|
+
email,
|
|
27
|
+
firstName: givenName,
|
|
28
|
+
lastName: familyName,
|
|
29
|
+
googleUserId: googleId,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
case "INVALID":
|
|
33
|
+
googleSignOut();
|
|
34
|
+
return showMessage({
|
|
35
|
+
message: `You have previously registered using your ${result.data.existingLoginType}. Please use your ${result.data.existingLoginType} to login method.`,
|
|
36
|
+
type: "error",
|
|
37
|
+
});
|
|
38
|
+
case "SUCCESS":
|
|
39
|
+
onLogin({
|
|
40
|
+
token: result.data.token,
|
|
41
|
+
member: result.data.member
|
|
42
|
+
});
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const onFailure = (response) => {
|
|
47
|
+
if (response.error === "popup_closed_by_user") {
|
|
48
|
+
showMessage({
|
|
49
|
+
message: "If you tried logging in with Google and seeing this error then please enable third-party cookies and try again.",
|
|
50
|
+
type: "warning"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const { signIn } = useGoogleLogin({
|
|
55
|
+
clientId: googleAppId || "",
|
|
56
|
+
onSuccess,
|
|
57
|
+
onFailure,
|
|
58
|
+
isSignedIn: false,
|
|
59
|
+
accessType: "offline",
|
|
60
|
+
cookiePolicy: "single_host_origin",
|
|
61
|
+
});
|
|
62
|
+
const loginWithGoogle = useCallback(() => {
|
|
63
|
+
if (!googleAppId) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
return signIn();
|
|
67
|
+
}, [googleAppId, signIn]);
|
|
68
|
+
const googleSignOut = useCallback(() => {
|
|
69
|
+
const auth2 = window.gapi?.auth2?.getAuthInstance?.();
|
|
70
|
+
if (auth2) {
|
|
71
|
+
auth2.signOut().then(() => auth2.disconnect());
|
|
72
|
+
}
|
|
73
|
+
}, []);
|
|
74
|
+
return { loginWithGoogle, googleSignOut };
|
|
75
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Flex, ResponsiveModal, Typography } from "@truworth/twc-web-design";
|
|
3
|
+
const SocialLoginModal = ({ visible, hide, socialLoginType, onClick }) => {
|
|
4
|
+
return (_jsxs(ResponsiveModal, { title: _jsx(Typography, { type: "heading", size: "h6", className: "text-center mb-0", children: "Corporate Users" }), open: visible, onClose: hide, showCloseButton: false, children: [_jsxs(Typography, { type: "body", size: "small", children: ["If you are a corporate user, you will", ' ', _jsx("span", { style: { textDecoration: 'underline' }, children: "NOT" }), ' ', "be able to avail all the health benefits offered on The Wellness Corner by your employer if you login through ", socialLoginType, ".", _jsxs(Flex, { direction: "column", className: "mt-3", children: ["In that case you will have to go back ", '&', " register using your official Email Address."] })] }), _jsx(Button, { label: "I understand, I'm NOT a corporate user", className: "mt-4", onClick: (e) => {
|
|
5
|
+
e.preventDefault();
|
|
6
|
+
onClick();
|
|
7
|
+
} })] }));
|
|
8
|
+
};
|
|
9
|
+
export { SocialLoginModal };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
import { Text } from 'react-native';
|
|
4
|
+
import { BottomSheet, RoundedButton } from '@truworth/twc-rn-common';
|
|
5
|
+
import { useModalize } from 'react-native-modalize';
|
|
6
|
+
const SocialLoginModal = ({ visible, hide, socialLoginType, onClick }) => {
|
|
7
|
+
const { ref: socialSheetRef, open: openSheet, close: closeSheet } = useModalize();
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (visible) {
|
|
10
|
+
openSheet();
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
closeSheet();
|
|
14
|
+
}
|
|
15
|
+
}, [visible]);
|
|
16
|
+
return (_jsxs(BottomSheet, { title: 'Corporate Users', sheetRef: socialSheetRef, childrenStyle: { padding: 16 }, onClose: hide, children: [_jsxs(Text, { style: {
|
|
17
|
+
fontSize: 12, fontWeight: '600', color: '#000',
|
|
18
|
+
lineHeight: 16, marginBottom: 30,
|
|
19
|
+
}, children: ["If you are a corporate user, you will", ' ', _jsx(Text, { style: { textDecorationLine: 'underline' }, children: "NOT" }), ' ', "be able to avail all the health benefits offered on The Wellness Corner by your employer if you login through ", socialLoginType, ".", '\n', '\n', "In that case you will have to go back ", '&', " register using your official Email Address."] }), _jsx(RoundedButton, { label: "I understand, I'm NOT a corporate user", onPress: onClick })] }));
|
|
20
|
+
};
|
|
21
|
+
export { SocialLoginModal };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import { useAuthContext } from "../../../../hooks/useAuthContext";
|
|
3
2
|
import { useAuthPackageContext } from "../../../../hooks/internal/useAuthPackageContext";
|
|
3
|
+
import { SocialLoginsEnum } from "../../../../enums";
|
|
4
|
+
import { socialLoginOptions } from "../../../../constants/social-login-options";
|
|
4
5
|
/**
|
|
5
6
|
* @internal
|
|
6
7
|
* Hook for managing Welcome screen state and auth context integration.
|
|
@@ -9,13 +10,17 @@ import { useAuthPackageContext } from "../../../../hooks/internal/useAuthPackage
|
|
|
9
10
|
const useWelcome = () => {
|
|
10
11
|
const [loading, setLoading] = useState(false);
|
|
11
12
|
const [showSocialLoginModal, setShowSocialLoginModal] = useState(false);
|
|
12
|
-
const
|
|
13
|
+
const [socialLoginType, setSocialLoginType] = useState();
|
|
14
|
+
const { LogoComponent, socialLoginConfig, onLaunchAuthSession } = useAuthPackageContext();
|
|
15
|
+
const isSocialLoginEnabled = socialLoginOptions.some(({ type }) => socialLoginConfig?.[type]?.enabled);
|
|
13
16
|
return {
|
|
14
|
-
loading,
|
|
15
|
-
setLoading,
|
|
17
|
+
loading, setLoading,
|
|
16
18
|
showSocialLoginModal, setShowSocialLoginModal,
|
|
19
|
+
socialLoginType, setSocialLoginType,
|
|
17
20
|
LogoComponent,
|
|
18
|
-
onLaunchAuthSession
|
|
21
|
+
onLaunchAuthSession,
|
|
22
|
+
socialLoginConfig,
|
|
23
|
+
isSocialLoginEnabled,
|
|
19
24
|
};
|
|
20
25
|
};
|
|
21
26
|
export { useWelcome };
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useCallback } from 'react';
|
|
3
3
|
import { useFocusEffect } from '@react-navigation/native';
|
|
4
|
-
import { View, Text, BackHandler, ScrollView } from 'react-native';
|
|
4
|
+
import { View, Text, BackHandler, ScrollView, TouchableOpacity, Platform } from 'react-native';
|
|
5
5
|
import { Colors, LoadingModal, RoundedButton } from '@truworth/twc-rn-common';
|
|
6
6
|
import { Layout } from '@ui-kitten/components';
|
|
7
7
|
import { useWelcome } from './hooks/internal/useWelcome';
|
|
8
|
+
import { SocialLoginModal } from './components/SocialLoginModal';
|
|
9
|
+
import { socialLoginOptions } from '../../constants/social-login-options';
|
|
8
10
|
import Logo from '../../../assets/logo.svg';
|
|
11
|
+
import { useSocialAuth } from './SocialAuth/hooks/useSocialAuth.native';
|
|
12
|
+
import { SocialLoginsEnum } from '../../enums';
|
|
13
|
+
import FastImage from 'react-native-fast-image';
|
|
9
14
|
const { gray } = Colors;
|
|
10
15
|
const Welcome = ({ navigation }) => {
|
|
11
|
-
const { loading,
|
|
16
|
+
const { loading, loginWithFacebook, loginWithGoogle, loginWithApple } = useSocialAuth();
|
|
17
|
+
const { LogoComponent, isSocialLoginEnabled, showSocialLoginModal, setShowSocialLoginModal, socialLoginType, setSocialLoginType, socialLoginConfig, onLaunchAuthSession, } = useWelcome();
|
|
12
18
|
useFocusEffect(useCallback(() => {
|
|
13
19
|
onLaunchAuthSession?.();
|
|
14
20
|
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => true);
|
|
@@ -30,12 +36,43 @@ const Welcome = ({ navigation }) => {
|
|
|
30
36
|
// Fallback to default logo
|
|
31
37
|
return (_jsx(View, { style: { marginTop: 70, alignSelf: 'center' }, children: _jsx(Logo, {}) }));
|
|
32
38
|
};
|
|
39
|
+
const handleLogin = () => {
|
|
40
|
+
setShowSocialLoginModal(false);
|
|
41
|
+
switch (socialLoginType) {
|
|
42
|
+
case SocialLoginsEnum.Google:
|
|
43
|
+
return loginWithGoogle();
|
|
44
|
+
case SocialLoginsEnum.Facebook:
|
|
45
|
+
return loginWithFacebook();
|
|
46
|
+
case SocialLoginsEnum.Apple:
|
|
47
|
+
return loginWithApple();
|
|
48
|
+
default:
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
33
52
|
return (_jsxs(Layout, { style: { flex: 1, backgroundColor: '#FFFFFF' }, children: [_jsx(ScrollView, { showsVerticalScrollIndicator: false, children: _jsxs(View, { style: { padding: 16 }, children: [renderLogo(), _jsx(Text, { style: {
|
|
34
53
|
fontSize: 32, fontWeight: '600', color: gray.gray_900,
|
|
35
54
|
marginTop: 56, textAlign: 'center', lineHeight: 48,
|
|
36
55
|
}, children: "Welcome!" }), _jsx(Text, { style: {
|
|
37
56
|
fontSize: 16, fontWeight: '500', color: gray.gray_400,
|
|
38
57
|
marginTop: 4, lineHeight: 24, textAlign: 'center',
|
|
39
|
-
}, children: "Select an option to proceed" }), _jsxs(View, { style: { marginTop: 48 }, children: [_jsx(RoundedButton, { type: "secondary", label: "Enter Your Email", leftIcon: "mail", size: "large", onPress: () => navigation.navigate('EnterEmail') }), _jsx(RoundedButton, { type: "outline", label: "Search Your Organization", leftIcon: "key", containerStyle: { marginTop: 24 }, size: "large", onPress: () => navigation.navigate('SSOSearchOrganization') })] })
|
|
58
|
+
}, children: "Select an option to proceed" }), _jsxs(View, { style: { marginTop: 48 }, children: [_jsx(RoundedButton, { type: "secondary", label: "Enter Your Email", leftIcon: "mail", size: "large", onPress: () => navigation.navigate('EnterEmail') }), _jsx(RoundedButton, { type: "outline", label: "Search Your Organization", leftIcon: "key", containerStyle: { marginTop: 24 }, size: "large", onPress: () => navigation.navigate('SSOSearchOrganization') })] }), isSocialLoginEnabled && (_jsxs(_Fragment, { children: [_jsxs(View, { style: {
|
|
59
|
+
flexDirection: 'row', alignItems: 'center',
|
|
60
|
+
justifyContent: 'center', marginVertical: 40
|
|
61
|
+
}, children: [_jsx(View, { style: { height: 1, width: 20, backgroundColor: gray.gray_600 } }), _jsx(Text, { style: { fontSize: 14, color: gray.gray_600, paddingHorizontal: 3, fontWeight: '500' }, children: ` or continue with ` }), _jsx(View, { style: { height: 1, width: 20, backgroundColor: gray.gray_600 } })] }), _jsx(View, { style: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }, children: socialLoginOptions.map(({ type, iconImageUrl }) => {
|
|
62
|
+
if (Platform.OS === 'android' && type === SocialLoginsEnum.Apple) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return (socialLoginConfig?.[type]?.enabled &&
|
|
66
|
+
_jsx(TouchableOpacity, { activeOpacity: 0.8, onPress: () => {
|
|
67
|
+
setSocialLoginType(type);
|
|
68
|
+
setShowSocialLoginModal(true);
|
|
69
|
+
}, style: {
|
|
70
|
+
height: 56, backgroundColor: '#FFFFFF', width: 56,
|
|
71
|
+
flexDirection: 'row', alignItems: 'center',
|
|
72
|
+
justifyContent: 'center', marginHorizontal: 12,
|
|
73
|
+
borderWidth: 1, borderColor: gray.gray_200, borderRadius: 28,
|
|
74
|
+
}, children: _jsx(FastImage, { source: { uri: iconImageUrl }, style: { width: 24, height: 24 } }) }, type));
|
|
75
|
+
}) })] }))] }) }), showSocialLoginModal &&
|
|
76
|
+
_jsx(SocialLoginModal, { visible: showSocialLoginModal, hide: () => setShowSocialLoginModal(false), socialLoginType: socialLoginType, onClick: handleLogin }), _jsx(LoadingModal, { loading: loading })] }));
|
|
40
77
|
};
|
|
41
78
|
export default Welcome;
|
|
@@ -30,8 +30,9 @@ declare const AuthPackageContext: React.Context<AuthPackageContextValue | null>;
|
|
|
30
30
|
* @param onLaunchAuthSession - Optional callback after successfully launching the auth session. For example, it can be used to set the Firebase notification token or request Android permissions.
|
|
31
31
|
* @param onRefreshSession - Optional handler for refreshing user session
|
|
32
32
|
* @param openChatSupport - Optional callback to open chat support
|
|
33
|
+
* @param socialLoginConfig - Optional social login configuration
|
|
33
34
|
*/
|
|
34
|
-
declare const AuthProvider: ({ children, LogoComponent, session, appConfig, onLogin, onLogout, onLaunchAuthSession, onRefreshSession, openChatSupport }: AuthContextProps) => import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
declare const AuthProvider: ({ children, LogoComponent, session, appConfig, onLogin, onLogout, onLaunchAuthSession, onRefreshSession, openChatSupport, socialLoginConfig }: AuthContextProps) => import("react/jsx-runtime").JSX.Element;
|
|
35
36
|
export { AuthProvider };
|
|
36
37
|
export default AuthContext;
|
|
37
38
|
export { AuthPackageContext };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import type { ClientProfile, MemberProfile, Profile, Partner, RegistrationMethod } from '../types/types';
|
|
3
|
+
import type { SocialLoginsEnum } from '../enums';
|
|
3
4
|
interface LoginResponse {
|
|
4
5
|
token: string;
|
|
5
6
|
member?: MemberProfile;
|
|
@@ -22,6 +23,10 @@ interface AuthContextProps {
|
|
|
22
23
|
sessionTimeout?: boolean;
|
|
23
24
|
};
|
|
24
25
|
appConfig?: AppConfig;
|
|
26
|
+
socialLoginConfig?: Partial<Record<SocialLoginsEnum, {
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
webClientId?: string;
|
|
29
|
+
}>>;
|
|
25
30
|
onLogin: (result: LoginResponse) => void;
|
|
26
31
|
onLogout: () => Promise<any>;
|
|
27
32
|
onRefreshSession?: (result: RefreshSessionResponse) => Promise<void>;
|
|
@@ -42,6 +47,10 @@ interface AuthPackageContextValue {
|
|
|
42
47
|
token: string;
|
|
43
48
|
LogoComponent?: React.ComponentType | React.ReactElement;
|
|
44
49
|
appConfig: AppConfig;
|
|
50
|
+
socialLoginConfig?: Partial<Record<SocialLoginsEnum, {
|
|
51
|
+
enabled?: boolean;
|
|
52
|
+
webClientId?: string;
|
|
53
|
+
}>>;
|
|
45
54
|
onTokenChange: (token: string) => void;
|
|
46
55
|
onLogin: (result: LoginResponse) => void;
|
|
47
56
|
logout: () => void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SocialLoginsEnum } from "../enums";
|
|
1
2
|
import type { RefreshSessionResponse } from "../contexts/type";
|
|
2
3
|
import type { AxiosRequestConfig } from "axios";
|
|
3
4
|
interface RequestOptionsProps extends AxiosRequestConfig {
|
|
@@ -10,4 +11,9 @@ interface NetworkProps {
|
|
|
10
11
|
onRefreshSession?: (token: string) => Promise<any>;
|
|
11
12
|
onLogout?: () => Promise<any>;
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
interface SocialLoginOptions {
|
|
15
|
+
type: SocialLoginsEnum;
|
|
16
|
+
icon?: React.ReactNode;
|
|
17
|
+
iconImageUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
export type { RequestOptionsProps, NetworkProps, SocialLoginOptions };
|
|
@@ -17,6 +17,9 @@ export type AuthStackParamList = {
|
|
|
17
17
|
registrationToken?: string;
|
|
18
18
|
clientId?: number | string;
|
|
19
19
|
registrationMethod?: string;
|
|
20
|
+
googleUserId?: string;
|
|
21
|
+
fbUserId?: string;
|
|
22
|
+
appleUserId?: string;
|
|
20
23
|
};
|
|
21
24
|
CountryCode: {
|
|
22
25
|
prevRoute: 'LoginMobile' | 'MobileNumber' | 'SignUp' | 'EnterMobile';
|
|
@@ -12,7 +12,6 @@ declare const useEnterEmail: () => {
|
|
|
12
12
|
handleClearEmail: () => void;
|
|
13
13
|
isLoginConflictModalVisible: boolean;
|
|
14
14
|
setIsLoginConflictModalVisible: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
15
|
-
onRegistrationMethodChange: (method: import("../../../../types/types").RegistrationMethod) => void;
|
|
16
15
|
appName: string | undefined;
|
|
17
16
|
loginConflictTitle: string;
|
|
18
17
|
loginConflictDescription: string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { MemberProfile } from "../../../types/types";
|
|
2
|
+
export declare enum SocialAuthStatus {
|
|
3
|
+
REGISTER = "REGISTER",
|
|
4
|
+
INVALID = "INVALID",
|
|
5
|
+
SUCCESS = "SUCCESS"
|
|
6
|
+
}
|
|
7
|
+
type Platform = "web" | "ios" | "android";
|
|
8
|
+
interface BaseSocialLoginParams {
|
|
9
|
+
email: string;
|
|
10
|
+
platform: Platform;
|
|
11
|
+
}
|
|
12
|
+
export interface GoogleLoginParams extends BaseSocialLoginParams {
|
|
13
|
+
googleIdentityToken: string;
|
|
14
|
+
}
|
|
15
|
+
export interface FacebookLoginParams extends BaseSocialLoginParams {
|
|
16
|
+
facebookIdentityToken: string;
|
|
17
|
+
}
|
|
18
|
+
export interface AppleLoginParams extends BaseSocialLoginParams {
|
|
19
|
+
appleIdentityToken: string;
|
|
20
|
+
}
|
|
21
|
+
export type SocialAuthResult = {
|
|
22
|
+
status: SocialAuthStatus.REGISTER;
|
|
23
|
+
data: {
|
|
24
|
+
email: string;
|
|
25
|
+
};
|
|
26
|
+
} | {
|
|
27
|
+
status: SocialAuthStatus.INVALID;
|
|
28
|
+
data: {
|
|
29
|
+
existingLoginType?: string;
|
|
30
|
+
};
|
|
31
|
+
} | {
|
|
32
|
+
status: SocialAuthStatus.SUCCESS;
|
|
33
|
+
data: {
|
|
34
|
+
token: string;
|
|
35
|
+
member: MemberProfile;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Handles Google authentication
|
|
40
|
+
*/
|
|
41
|
+
export declare const handleGoogleAuth: ({ email, googleIdentityToken, platform, }: GoogleLoginParams) => Promise<SocialAuthResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Handles Facebook authentication
|
|
44
|
+
*/
|
|
45
|
+
export declare const handleFacebookAuth: ({ email, facebookIdentityToken, platform }: FacebookLoginParams) => Promise<SocialAuthResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Handles Apple authentication
|
|
48
|
+
*/
|
|
49
|
+
export declare const handleAppleAuth: ({ email, appleIdentityToken, platform }: AppleLoginParams) => Promise<SocialAuthResult>;
|
|
50
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SocialLoginsEnum } from "../../../../enums";
|
|
1
2
|
/**
|
|
2
3
|
* @internal
|
|
3
4
|
* Hook for managing Welcome screen state and auth context integration.
|
|
@@ -8,7 +9,14 @@ declare const useWelcome: () => {
|
|
|
8
9
|
setLoading: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
9
10
|
showSocialLoginModal: boolean;
|
|
10
11
|
setShowSocialLoginModal: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
12
|
+
socialLoginType: SocialLoginsEnum | undefined;
|
|
13
|
+
setSocialLoginType: import("react").Dispatch<import("react").SetStateAction<SocialLoginsEnum | undefined>>;
|
|
11
14
|
LogoComponent: import("react").ComponentType<{}> | import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
|
|
12
15
|
onLaunchAuthSession: (() => void) | undefined;
|
|
16
|
+
socialLoginConfig: Partial<Record<SocialLoginsEnum, {
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
webClientId?: string;
|
|
19
|
+
}>> | undefined;
|
|
20
|
+
isSocialLoginEnabled: boolean;
|
|
13
21
|
};
|
|
14
22
|
export { useWelcome };
|
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.
|
|
7
|
+
"version": "1.2.6",
|
|
8
8
|
"main": "build/src/index.js",
|
|
9
9
|
"types": "build/types/index.d.ts",
|
|
10
10
|
"files": [
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"@invertase/react-native-apple-authentication": "^1.1.2",
|
|
24
24
|
"@react-native-clipboard/clipboard": "1.14.1",
|
|
25
25
|
"@react-native-community/datetimepicker": "8.1.1",
|
|
26
|
+
"@react-native-google-signin/google-signin": "^16.0.0",
|
|
26
27
|
"@react-navigation/native": "^6.1.17",
|
|
27
28
|
"@react-navigation/native-stack": "^6.10.0",
|
|
28
29
|
"@truworth/twc-rn-common": "^1.0.15",
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"next": "^15.0.4",
|
|
49
50
|
"react": "^18.2.0",
|
|
50
51
|
"react-dom": "^18.2.0",
|
|
52
|
+
"react-google-login": "^5.2.2",
|
|
51
53
|
"react-google-recaptcha": "^3.1.0",
|
|
52
54
|
"react-infinite-scroll-component": "^6.1.0",
|
|
53
55
|
"react-lottie": "^1.2.3",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|