feat: login with google added
This commit is contained in:
57
index.html
57
index.html
@@ -1,29 +1,32 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Harmony club</title>
|
||||
<!-- this script add for preventing initial theme flashing -->
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
const THEME_STORAGE_KEY = 'mui-mode';
|
||||
const DARK_THEME_CLASS_NAME = 'dark';
|
||||
const savedMode = localStorage.getItem(THEME_STORAGE_KEY);
|
||||
const prefersDark = window.matchMedia(
|
||||
'(prefers-color-scheme: dark)',
|
||||
).matches;
|
||||
if (savedMode === 'dark' || (!savedMode && prefersDark)) {
|
||||
document.documentElement.classList.add(DARK_THEME_CLASS_NAME);
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Harmony club</title>
|
||||
<!-- this script add for preventing initial theme flashing -->
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
const THEME_STORAGE_KEY = 'mui-mode';
|
||||
const DARK_THEME_CLASS_NAME = 'dark';
|
||||
const savedMode = localStorage.getItem(THEME_STORAGE_KEY);
|
||||
const prefersDark = window.matchMedia(
|
||||
'(prefers-color-scheme: dark)',
|
||||
).matches;
|
||||
if (savedMode === 'dark' || (!savedMode && prefersDark)) {
|
||||
document.documentElement.classList.add(DARK_THEME_CLASS_NAME);
|
||||
}
|
||||
} catch (e) { }
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -13,8 +13,13 @@ import {
|
||||
import { UserStatus } from '../../types/userTypes';
|
||||
import type { CountryCode, GUID } from '@/types/commonTypes';
|
||||
import { VerifyPhoneNumber } from './VerifyPhoneNumber';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
export const AuthenticationSteps = (): JSX.Element => {
|
||||
const DEFAULT_RETURN_URL = 'https://account.business-harmony.com/';
|
||||
const [searchParams] = useSearchParams();
|
||||
const authReturnUrl: string =
|
||||
searchParams.get('returnUrl') ?? DEFAULT_RETURN_URL;
|
||||
const [authMode, setAuthMode] = useState<AuthMode>('register');
|
||||
const [authType, setAuthType] = useState<AuthType>('phone');
|
||||
const [currentStep, setCurrentStep] = useState<
|
||||
@@ -55,50 +60,26 @@ export const AuthenticationSteps = (): JSX.Element => {
|
||||
const handleOTPVerfied = (
|
||||
registeredWithoutPhoneNumber: boolean = false,
|
||||
userId: GUID,
|
||||
returnUrl?: string,
|
||||
) => {
|
||||
localStorage.setItem('userID', userId);
|
||||
// if (registeredWithoutPhoneNumber) {
|
||||
// setCurrentStep('addPhoneNumber');
|
||||
// }
|
||||
|
||||
if (returnUrl) {
|
||||
location.href = returnUrl;
|
||||
if (registeredWithoutPhoneNumber) {
|
||||
setCurrentStep('addPhoneNumber');
|
||||
}
|
||||
|
||||
handleUserLoggedIn(userId);
|
||||
};
|
||||
|
||||
const handleEditValue = () => {
|
||||
setCurrentStep('emailOrPhone');
|
||||
};
|
||||
|
||||
const handleCompleteSignUp = (countryCode: string, value: string) => {
|
||||
setCurrentStep('addedPhoneNumberVerify');
|
||||
};
|
||||
|
||||
const handleCompleteSignUpOTPVerified = () => {
|
||||
console.log('phoneNumberVerified');
|
||||
};
|
||||
|
||||
const handleCompleteSignUpEditValue = () => {
|
||||
setCurrentStep('emailOrPhone');
|
||||
};
|
||||
|
||||
const handleLoggedInWithPassowrd = (userId: GUID, returnUrl?: string) => {
|
||||
const handleUserLoggedIn = (userId: GUID) => {
|
||||
localStorage.setItem('userID', userId);
|
||||
|
||||
if (returnUrl) {
|
||||
location.href = returnUrl;
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginWithOtpInsteadOfPassword = async () => {
|
||||
setCurrentStep('verify');
|
||||
location.href = authReturnUrl;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{currentStep === 'emailOrPhone' && (
|
||||
<LoginRegisterForm
|
||||
authReturnUrl={authReturnUrl}
|
||||
onGoogleAuthenticated={handleUserLoggedIn}
|
||||
countryCode={countryCode}
|
||||
setCountryCode={setCountryCode}
|
||||
loginRegisterValue={loginRegisterValue}
|
||||
@@ -111,9 +92,10 @@ export const AuthenticationSteps = (): JSX.Element => {
|
||||
|
||||
{currentStep === 'verify' && (
|
||||
<OtpVerifyForm
|
||||
authReturnUrl={authReturnUrl}
|
||||
countryCode={countryCode}
|
||||
onOTPVerified={handleOTPVerfied}
|
||||
onEditValue={handleEditValue}
|
||||
onEditValue={() => setCurrentStep('emailOrPhone')}
|
||||
authMode={authMode}
|
||||
authType={authType}
|
||||
value={loginRegisterValue}
|
||||
@@ -122,12 +104,13 @@ export const AuthenticationSteps = (): JSX.Element => {
|
||||
|
||||
{currentStep === 'enterPassword' && (
|
||||
<EnterPasswordForm
|
||||
authReturnUrl={authReturnUrl}
|
||||
loginRegisterValue={loginRegisterValue}
|
||||
countryCode={countryCode}
|
||||
authType={authType}
|
||||
onLoggedIn={handleLoggedInWithPassowrd}
|
||||
onEditValue={handleEditValue}
|
||||
onLoginWithOTP={handleLoginWithOtpInsteadOfPassword}
|
||||
onLoggedIn={handleUserLoggedIn}
|
||||
onEditValue={() => setCurrentStep('emailOrPhone')}
|
||||
onLoginWithOTP={() => setCurrentStep('verify')}
|
||||
emailOrPhone={loginRegisterValue}
|
||||
/>
|
||||
)}
|
||||
@@ -137,16 +120,16 @@ export const AuthenticationSteps = (): JSX.Element => {
|
||||
value={addedPhoneNumberValue}
|
||||
setValue={setAddedPhoneNumberValue}
|
||||
email={loginRegisterValue}
|
||||
onCompleteSignUp={handleCompleteSignUp}
|
||||
onCompleteSignUp={() => setCurrentStep('addedPhoneNumberVerify')}
|
||||
/>
|
||||
)}
|
||||
|
||||
{currentStep === 'addedPhoneNumberVerify' && (
|
||||
<VerifyPhoneNumber
|
||||
countryCode={countryCode}
|
||||
onEditValue={handleCompleteSignUpEditValue}
|
||||
onEditValue={() => setCurrentStep('emailOrPhone')}
|
||||
value={addedPhoneNumberValue}
|
||||
onPhoneNumberVerified={handleCompleteSignUpOTPVerified}
|
||||
onPhoneNumberVerified={handleUserLoggedIn}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -24,11 +24,12 @@ import type { LoginRequest, PasswordLoginRequest } from '../../types/userTypes';
|
||||
export interface EnterPasswordFormProps {
|
||||
onEditValue: () => void;
|
||||
onLoginWithOTP: () => void;
|
||||
onLoggedIn: (userId: GUID, returnUrl?: string) => void;
|
||||
onLoggedIn: (userId: GUID) => void;
|
||||
emailOrPhone: string;
|
||||
authType: AuthType;
|
||||
loginRegisterValue: string;
|
||||
countryCode: CountryCode;
|
||||
authReturnUrl: string;
|
||||
}
|
||||
|
||||
export const EnterPasswordForm = ({
|
||||
@@ -39,9 +40,8 @@ export const EnterPasswordForm = ({
|
||||
authType,
|
||||
loginRegisterValue,
|
||||
countryCode,
|
||||
authReturnUrl,
|
||||
}: EnterPasswordFormProps) => {
|
||||
const DEFAULT_RETURN_URL = 'https://account.business-harmony.com/';
|
||||
const [searchParams] = useSearchParams();
|
||||
const { t } = useTranslation('authentication');
|
||||
const [passValue, setPassValue] = useState<string>('');
|
||||
const [inputTouched, setInputTouched] = useState<boolean>(false);
|
||||
@@ -68,14 +68,14 @@ export const EnterPasswordForm = ({
|
||||
authType === 'phone' ? countryCode + loginRegisterValue : undefined,
|
||||
email: authType === 'email' ? loginRegisterValue : undefined,
|
||||
password: passValue,
|
||||
returnUrl: searchParams.get('returnUrl') ?? DEFAULT_RETURN_URL,
|
||||
returnUrl: authReturnUrl,
|
||||
};
|
||||
const result = await loginWithPassword(apiRequest);
|
||||
const jsonRes = await result.json();
|
||||
|
||||
if (jsonRes.success) {
|
||||
setLoginStatus('success');
|
||||
onLoggedIn(jsonRes.userId, jsonRes.returnUrl);
|
||||
onLoggedIn(jsonRes.userId);
|
||||
} else {
|
||||
setLoginStatus('failed');
|
||||
setLoginFailedMessage(jsonRes.message);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Button } from '@mui/material';
|
||||
import { Google } from 'iconsax-reactjs';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { GoogleCodeClientResponse } from '../../types/userTypes';
|
||||
import { loginOrSignUpWithGoogle } from '../../api/authorizationAPI';
|
||||
import type { GUID } from '@/types/commonTypes';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
google: typeof google;
|
||||
}
|
||||
const google: any;
|
||||
}
|
||||
|
||||
export interface GoogleAuthenticationProps {
|
||||
disabled: boolean;
|
||||
authReturnUrl: string;
|
||||
onGoogleAuthenticated: (userId: GUID) => void;
|
||||
}
|
||||
|
||||
export const GoogleAuthentication = ({
|
||||
disabled,
|
||||
authReturnUrl,
|
||||
onGoogleAuthenticated,
|
||||
}: GoogleAuthenticationProps) => {
|
||||
const { t } = useTranslation('authentication');
|
||||
const [loginWithGoogleLoading, setLoginWithGoogleLoading] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const clientRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://accounts.google.com/gsi/client';
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
document.body.appendChild(script);
|
||||
|
||||
script.onload = () => {
|
||||
clientRef.current = google.accounts.oauth2.initCodeClient({
|
||||
client_id: 'CLIEND_ID',
|
||||
scope: 'openid email profile',
|
||||
ux_mode: 'popup',
|
||||
response_type: 'id_token',
|
||||
callback: async (resp: GoogleCodeClientResponse) => {
|
||||
setLoginWithGoogleLoading(true);
|
||||
|
||||
const result = await loginOrSignUpWithGoogle({
|
||||
idToken: resp.id_token,
|
||||
returnUrl: authReturnUrl,
|
||||
});
|
||||
const jsonRes = await result.json();
|
||||
|
||||
if (jsonRes.success) {
|
||||
onGoogleAuthenticated(jsonRes.userId);
|
||||
} else {
|
||||
handleGoogleLogin();
|
||||
}
|
||||
|
||||
setLoginWithGoogleLoading(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleGoogleLogin = () => {
|
||||
if (clientRef.current) {
|
||||
clientRef.current.requestCode();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleGoogleLogin}
|
||||
disabled={disabled}
|
||||
loading={loginWithGoogleLoading}
|
||||
variant="outlined"
|
||||
startIcon={<Google variant="Bold" />}
|
||||
>
|
||||
{t('loginForm.loginWithGoogle')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -18,7 +18,8 @@ import { CountryCodeSelector } from '../CountryCodeSelector';
|
||||
import type { UserStatus } from '../../types/userTypes';
|
||||
import { getUserStatusByPhoneNumberOrEmail } from '../../api/authorizationAPI';
|
||||
import { Toast } from '@/components/Toast';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import type { CountryCode, GUID } from '@/types/commonTypes';
|
||||
import { GoogleAuthentication } from './GoogleAuthentication';
|
||||
|
||||
export interface LoginRegisterFormProps {
|
||||
loginRegisterValue: string;
|
||||
@@ -28,6 +29,8 @@ export interface LoginRegisterFormProps {
|
||||
authType: AuthType;
|
||||
setAuthType: Dispatch<AuthType>;
|
||||
onLoginRegisterSubmit: (value: string, userStatus: UserStatus) => void;
|
||||
authReturnUrl: string;
|
||||
onGoogleAuthenticated: (userId: GUID) => void;
|
||||
}
|
||||
|
||||
export function LoginRegisterForm({
|
||||
@@ -38,6 +41,8 @@ export function LoginRegisterForm({
|
||||
authType,
|
||||
setAuthType,
|
||||
onLoginRegisterSubmit,
|
||||
authReturnUrl,
|
||||
onGoogleAuthenticated,
|
||||
}: LoginRegisterFormProps) {
|
||||
const [checkStatusLoading, setCheckStatusLoading] = useState<boolean>(false);
|
||||
const { t, i18n } = useTranslation('authentication');
|
||||
@@ -172,13 +177,12 @@ export function LoginRegisterForm({
|
||||
<Button loading={checkStatusLoading} onClick={handleSubmit}>
|
||||
{t('loginForm.submitButton')}
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
<GoogleAuthentication
|
||||
authReturnUrl={authReturnUrl}
|
||||
onGoogleAuthenticated={onGoogleAuthenticated}
|
||||
disabled={checkStatusLoading}
|
||||
variant="outlined"
|
||||
startIcon={<Google variant="Bold" />}
|
||||
>
|
||||
{t('loginForm.loginWithGoogle')}
|
||||
</Button>
|
||||
/>
|
||||
</Stack>
|
||||
</AuthenticationCard>
|
||||
);
|
||||
|
||||
@@ -21,11 +21,8 @@ interface OtpVerifyFormProps {
|
||||
authType: AuthType;
|
||||
authMode: AuthMode;
|
||||
onEditValue: () => void;
|
||||
onOTPVerified: (
|
||||
registeredWithoutPhoneNumber: boolean,
|
||||
userID: GUID,
|
||||
returnUrl?: string,
|
||||
) => void;
|
||||
onOTPVerified: (registeredWithoutPhoneNumber: boolean, userID: GUID) => void;
|
||||
authReturnUrl: string;
|
||||
}
|
||||
|
||||
export function OtpVerifyForm({
|
||||
@@ -35,9 +32,8 @@ export function OtpVerifyForm({
|
||||
authMode,
|
||||
onEditValue,
|
||||
onOTPVerified,
|
||||
authReturnUrl,
|
||||
}: OtpVerifyFormProps) {
|
||||
const DEFAULT_RETURN_URL = 'https://account.business-harmony.com/';
|
||||
const [searchParams] = useSearchParams();
|
||||
const [otpCode, setOtpCode] = useState<string>('');
|
||||
const [otpDigitInvalid, setOtpDigitInvalid] = useState<boolean>(false);
|
||||
const [verifyStatus, setVerifyStatus] = useState<'success' | 'failed'>();
|
||||
@@ -102,18 +98,14 @@ export function OtpVerifyForm({
|
||||
otpCode: otpCode,
|
||||
phoneNumber: authType === 'phone' ? countryCode + value : undefined,
|
||||
email: authType === 'email' ? value : undefined,
|
||||
returnUrl: searchParams.get('returnUrl') ?? DEFAULT_RETURN_URL,
|
||||
returnUrl: authReturnUrl,
|
||||
};
|
||||
const result = await loginOrSignUpWithOtp(loginRequest);
|
||||
const jsonRes = await result.json();
|
||||
|
||||
if (jsonRes.success) {
|
||||
setVerifyStatus('success');
|
||||
onOTPVerified(
|
||||
jsonRes.registeredWithOutPhoneNumber,
|
||||
jsonRes.userId,
|
||||
jsonRes.returnUrl,
|
||||
);
|
||||
onOTPVerified(jsonRes.registeredWithOutPhoneNumber, jsonRes.userId);
|
||||
} else {
|
||||
setVerifyStatus('failed');
|
||||
setErrorMessage(jsonRes.message);
|
||||
|
||||
@@ -13,13 +13,13 @@ import {
|
||||
sendEmailOtp,
|
||||
sendSmsOtp,
|
||||
} from '../../api/authorizationAPI';
|
||||
import type { CountryCode } from '@/types/commonTypes';
|
||||
import type { CountryCode, GUID } from '@/types/commonTypes';
|
||||
|
||||
interface VerifyPhoneNumberProps {
|
||||
value: string;
|
||||
countryCode: CountryCode;
|
||||
onEditValue: () => void;
|
||||
onPhoneNumberVerified: () => void;
|
||||
onPhoneNumberVerified: (userId: GUID) => void;
|
||||
}
|
||||
|
||||
export function VerifyPhoneNumber({
|
||||
|
||||
@@ -100,6 +100,10 @@ export interface ConfirmForgetPassCodeRequest {
|
||||
|
||||
// LoginOrSignUpWithGoogle
|
||||
|
||||
export interface GoogleCodeClientResponse {
|
||||
id_token: string;
|
||||
}
|
||||
|
||||
export interface LoginOrSignUpWithGoogleRequest {
|
||||
idToken: string;
|
||||
returnUrl: string;
|
||||
|
||||
Reference in New Issue
Block a user