From a4ee95832fb01ce314a10693a8efe2368b7b3b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D9=85=D9=87=D8=B1=D8=B2=D8=A7=D8=AF=20=D9=82=D8=AF=D8=B1?= =?UTF-8?q?=D8=AA=DB=8C?= Date: Sun, 10 Aug 2025 15:37:09 +0330 Subject: [PATCH] feat: login with google added --- index.html | 57 +++++++------ .../AuthenticationSteps.tsx | 61 +++++--------- .../AuthenticationSteps/EnterPasswordForm.tsx | 10 +-- .../GoogleAuthentication.tsx | 84 +++++++++++++++++++ .../AuthenticationSteps/LoginRegiserForm.tsx | 18 ++-- .../AuthenticationSteps/OtpVerifyForm.tsx | 18 ++-- .../AuthenticationSteps/VerifyPhoneNumber.tsx | 4 +- src/features/authorization/types/userTypes.ts | 4 + 8 files changed, 163 insertions(+), 93 deletions(-) create mode 100644 src/features/authorization/components/AuthenticationSteps/GoogleAuthentication.tsx diff --git a/index.html b/index.html index 6e5d786..c4f15da 100644 --- a/index.html +++ b/index.html @@ -1,29 +1,32 @@ - - - - - Harmony club - - - - -
- - - + + + + + + Harmony club + + + + + +
+ + + + \ No newline at end of file diff --git a/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx b/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx index c61f104..ce4f113 100644 --- a/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx +++ b/src/features/authorization/components/AuthenticationSteps/AuthenticationSteps.tsx @@ -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('register'); const [authType, setAuthType] = useState('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' && ( { {currentStep === 'verify' && ( setCurrentStep('emailOrPhone')} authMode={authMode} authType={authType} value={loginRegisterValue} @@ -122,12 +104,13 @@ export const AuthenticationSteps = (): JSX.Element => { {currentStep === 'enterPassword' && ( 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' && ( setCurrentStep('emailOrPhone')} value={addedPhoneNumberValue} - onPhoneNumberVerified={handleCompleteSignUpOTPVerified} + onPhoneNumberVerified={handleUserLoggedIn} /> )} diff --git a/src/features/authorization/components/AuthenticationSteps/EnterPasswordForm.tsx b/src/features/authorization/components/AuthenticationSteps/EnterPasswordForm.tsx index d7418ac..56dadd4 100644 --- a/src/features/authorization/components/AuthenticationSteps/EnterPasswordForm.tsx +++ b/src/features/authorization/components/AuthenticationSteps/EnterPasswordForm.tsx @@ -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(''); const [inputTouched, setInputTouched] = useState(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); diff --git a/src/features/authorization/components/AuthenticationSteps/GoogleAuthentication.tsx b/src/features/authorization/components/AuthenticationSteps/GoogleAuthentication.tsx new file mode 100644 index 0000000..c5c49cf --- /dev/null +++ b/src/features/authorization/components/AuthenticationSteps/GoogleAuthentication.tsx @@ -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(false); + + const clientRef = useRef(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 ( + + ); +}; diff --git a/src/features/authorization/components/AuthenticationSteps/LoginRegiserForm.tsx b/src/features/authorization/components/AuthenticationSteps/LoginRegiserForm.tsx index 3d64449..1f7aa5a 100644 --- a/src/features/authorization/components/AuthenticationSteps/LoginRegiserForm.tsx +++ b/src/features/authorization/components/AuthenticationSteps/LoginRegiserForm.tsx @@ -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; 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(false); const { t, i18n } = useTranslation('authentication'); @@ -172,13 +177,12 @@ export function LoginRegisterForm({ - + /> ); diff --git a/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx b/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx index 3c37714..baa6bc4 100644 --- a/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx +++ b/src/features/authorization/components/AuthenticationSteps/OtpVerifyForm.tsx @@ -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(''); const [otpDigitInvalid, setOtpDigitInvalid] = useState(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); diff --git a/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx b/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx index b047bdc..4236d59 100644 --- a/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx +++ b/src/features/authorization/components/AuthenticationSteps/VerifyPhoneNumber.tsx @@ -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({ diff --git a/src/features/authorization/types/userTypes.ts b/src/features/authorization/types/userTypes.ts index 22d974d..99eb998 100644 --- a/src/features/authorization/types/userTypes.ts +++ b/src/features/authorization/types/userTypes.ts @@ -100,6 +100,10 @@ export interface ConfirmForgetPassCodeRequest { // LoginOrSignUpWithGoogle +export interface GoogleCodeClientResponse { + id_token: string; +} + export interface LoginOrSignUpWithGoogleRequest { idToken: string; returnUrl: string;