From 10881598de194657ef15ab00eace756837ec7543 Mon Sep 17 00:00:00 2001 From: Ruslan Khayrullin Date: Thu, 20 Oct 2022 19:36:23 +0500 Subject: [PATCH] feat(ott-2872): auth with facebook --- Makefile | 2 + public/images/done.svg | 17 ++ src/config/env.tsx | 3 +- .../AuthServiceApp/components/Login/index.tsx | 4 +- .../components/Login/styled.tsx | 7 +- .../AuthServiceApp/components/Oauth/hooks.tsx | 79 ++++++++ .../AuthServiceApp/components/Oauth/index.tsx | 182 ++++++++++++++++-- .../components/Oauth/styled.tsx | 137 +++++++++++++ .../helpers/getAuthUrl/index.tsx | 21 +- src/helpers/getRandomString/index.tsx | 11 ++ src/helpers/index.tsx | 1 + 11 files changed, 440 insertions(+), 24 deletions(-) create mode 100644 public/images/done.svg create mode 100644 src/features/AuthServiceApp/components/Oauth/hooks.tsx create mode 100644 src/features/AuthServiceApp/components/Oauth/styled.tsx create mode 100644 src/helpers/getRandomString/index.tsx diff --git a/Makefile b/Makefile index 76e14f10..8345e806 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,7 @@ auth-build: REACT_APP_TYPE=auth-service \ REACT_APP_ENV=staging \ REACT_APP_GOOGLE_CLIENT_ID=1022673777479-43mqb9lsqkpcr47umuvsoo0eemfpljf5.apps.googleusercontent.com \ + REACT_APP_FACEBOOK_CLIENT_ID=798254931203361 \ BUILD_PATH=build_auth \ GENERATE_SOURCEMAP=false \ npx react-scripts build @@ -103,6 +104,7 @@ auth-production-build: REACT_APP_TYPE=auth-service \ REACT_APP_ENV=production \ REACT_APP_GOOGLE_CLIENT_ID=1022673777479-43mqb9lsqkpcr47umuvsoo0eemfpljf5.apps.googleusercontent.com \ + REACT_APP_FACEBOOK_CLIENT_ID=798254931203361 \ BUILD_PATH=build_auth \ GENERATE_SOURCEMAP=false \ npx react-scripts build diff --git a/public/images/done.svg b/public/images/done.svg new file mode 100644 index 00000000..97084d71 --- /dev/null +++ b/public/images/done.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/config/env.tsx b/src/config/env.tsx index ea7cc476..d3749e39 100644 --- a/src/config/env.tsx +++ b/src/config/env.tsx @@ -16,4 +16,5 @@ export const stageENV = process.env.REACT_APP_STAGE || 'staging' export const STRIPE_PUBLIC_KEY = process.env.REACT_APP_STRIPE_PK || 'pk_test_51J5TEYEDSxVnTgDWhKLstuDAhx9XmGJmj2awyZ1HghpWdU46MhXqbQt1PyW9XsRlES5JFyuQWbPRjoSsiW3wvXOH00KMirJEGZ' -export const GOOGLE_CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID || '1022673777479-43mqb9lsqkpcr47umuvsoo0eemfpljf5.apps.googleusercontent.com' +export const GOOGLE_CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID || '1043133237396-kebgih109kro71b5c7c8qphtgjbd2gdk.apps.googleusercontent.com' +export const FACEBOOK_CLIENT_ID = process.env.REACT_APP_FACEBOOK_CLIENT_ID || '798254931203361' diff --git a/src/features/AuthServiceApp/components/Login/index.tsx b/src/features/AuthServiceApp/components/Login/index.tsx index 089db6f4..5cf1c7cb 100644 --- a/src/features/AuthServiceApp/components/Login/index.tsx +++ b/src/features/AuthServiceApp/components/Login/index.tsx @@ -127,10 +127,10 @@ const Login = () => { Google - {/* + Facebook - */} + {/* Apple ID diff --git a/src/features/AuthServiceApp/components/Login/styled.tsx b/src/features/AuthServiceApp/components/Login/styled.tsx index 3d3012a5..d83a9cbd 100644 --- a/src/features/AuthServiceApp/components/Login/styled.tsx +++ b/src/features/AuthServiceApp/components/Login/styled.tsx @@ -90,12 +90,15 @@ export const AuthButtonImage = styled.div` return css` width: 32px; height: 32px; - background-image: url(/images/oauth/google.svg); - ` + background-image: url(/images/oauth/google.svg); ` case AuthProviders.Facebook: return css` + width: 32px; + height: 32px; background-image: url(/images/oauth/facebook.svg); + + ${isMobileDevice ? css`width: 57px;` : ''}; ` case AuthProviders.AppleID: diff --git a/src/features/AuthServiceApp/components/Oauth/hooks.tsx b/src/features/AuthServiceApp/components/Oauth/hooks.tsx new file mode 100644 index 00000000..5c94d4d7 --- /dev/null +++ b/src/features/AuthServiceApp/components/Oauth/hooks.tsx @@ -0,0 +1,79 @@ +import { + useEffect, + useRef, + useState, + useCallback, + FormEvent, + FocusEvent, + ChangeEvent, +} from 'react' + +import { isValidEmail } from 'features/AuthServiceApp/helpers/isValidEmail' + +import { API_ROOT } from '../../config/routes' + +export const useOauth = () => { + const [email, setEmail] = useState('') + const [error, setError] = useState('') + const [showEmailField, showShowEmailField] = useState(false) + + const formRef = useRef(null) + + const authorize = useCallback(async () => { + if (!formRef.current) return + + const url = `${API_ROOT}/oauth` + + const res = await fetch(url, { + body: new FormData(formRef.current), + method: 'POST', + }) + + if (res.ok && res.redirected) { + formRef.current.submit() + } else { + const data = await res.json() + + if (data.error === 'Token missing email field') { + showShowEmailField(true) + } + } + }, []) + + const handleEmailChange = ({ target: { value } }: ChangeEvent) => { + setEmail(value) + } + + const handleEmailFocus = () => { + setError('') + } + + const handleEmailBlur = ({ target: { value } }: FocusEvent) => { + if (!isValidEmail(value) && value) { + setError('error_invalid_email_format') + } + } + + const handleSubmit = (e: FormEvent) => { + e.preventDefault() + + if (error) return + + authorize() + } + + useEffect(() => { + authorize() + }, [authorize]) + + return { + email, + error, + formRef, + handleEmailBlur, + handleEmailChange, + handleEmailFocus, + handleSubmit, + showEmailField, + } +} diff --git a/src/features/AuthServiceApp/components/Oauth/index.tsx b/src/features/AuthServiceApp/components/Oauth/index.tsx index dd072031..124b9014 100644 --- a/src/features/AuthServiceApp/components/Oauth/index.tsx +++ b/src/features/AuthServiceApp/components/Oauth/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react' +import { Fragment } from 'react' import { Redirect } from 'react-router-dom' import { parse } from 'querystring' @@ -8,9 +8,25 @@ import { useLocalStore } from 'hooks' import type { Settings } from 'features/AuthStore/helpers' import { getClientSettings } from 'features/AuthStore/helpers' +import { T9n } from 'features/T9n' import { API_ROOT } from '../../config/routes' import { PAGES } from '../../config/pages' +import { useOauth } from './hooks' +import { + Header, + HeaderLogo, + Content, + DoneIcon, + Title, + Subtitle, + Form, + Input, + SubmitButton, + Text, + StyledLink, + Error, +} from './styled' const url = `${API_ROOT}/oauth` @@ -28,9 +44,7 @@ const Oauth = () => { validator: isObject, }) - const formRef = useRef(null) - - const idToken = parse(window.location.hash).id_token as string | undefined + const idToken = parse(window.location.hash.replace('#', '')).id_token as string | undefined const { client_id, @@ -40,28 +54,162 @@ const Oauth = () => { state, } = urlParams - useEffect(() => { - formRef.current?.submit() - }, []) + const { + email, + error, + formRef, + handleEmailBlur, + handleEmailChange, + handleEmailFocus, + handleSubmit, + showEmailField, + } = useOauth() if (!idToken || !client_id || !redirect_uri) return + if (showEmailField) { + return ( + +
+ +
+ + + You are one step away from creating inSports account! + Complete the registration form by entering your email: +
+ + + + + + + + + + + OK + + {error && ( + + + + )} +
+ + Also you can use any{' '} + other method of authorization + +
+
+ ) + } + return ( ) } diff --git a/src/features/AuthServiceApp/components/Oauth/styled.tsx b/src/features/AuthServiceApp/components/Oauth/styled.tsx new file mode 100644 index 00000000..b6586c0f --- /dev/null +++ b/src/features/AuthServiceApp/components/Oauth/styled.tsx @@ -0,0 +1,137 @@ +import { Link } from 'react-router-dom' + +import styled, { css } from 'styled-components/macro' + +import { isMobileDevice } from 'config/userAgent' + +import { ButtonSolid } from 'features/Common' +import { Error as ErrorBase } from 'features/AuthServiceApp/styled' +import { HeaderLogo as HeaderLogoBase } from 'features/ProfileHeader/styled' + +import { Input as InputBase } from 'components/Input' + +export const Header = styled.div` + position: absolute; + padding: 30px 25px 0; +` + +export const HeaderLogo = styled(HeaderLogoBase)` + ${isMobileDevice + ? css` + top: 20px; + ` + : ''}; +` + +export const Content = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100vh; + padding: 20px 25px; + text-align: center; +` + +export const DoneIcon = styled.div` + width: 88px; + height: 88px; + margin-bottom: 20px; + background-image: url(/images/done.svg); + background-size: cover; + + ${isMobileDevice + ? css` + width: 65px; + height: 65px; + ` + : ''}; +` + +export const Title = styled.div` + margin-bottom: 40px; + font-size: 24px; + font-weight: bold; + color: ${({ theme }) => theme.colors.white}; + + ${isMobileDevice + ? css` + margin-bottom: 15px; + line-height: 20px; + font-size: 16px; + ` + : ''}; +` + +export const Subtitle = styled.div` + margin-bottom: 40px; + font-size: 18px; + line-height: 18px; + color: ${({ theme }) => theme.colors.white}; + + ${isMobileDevice + ? css` + margin-bottom: 15px; + font-size: 12px; + ` + : ''}; +` + +export const Form = styled.form` + position: relative; + display: flex; + gap: 20px; + margin-bottom: 30px; + + ${isMobileDevice + ? css` + gap: 10px; + width: 100%; + flex-direction: column; + ` + : ''}; +` + +export const Input = styled(InputBase)` + width: 406px; + height: 50px; + + ${isMobileDevice + ? css` + width: 100%; + height: 32px; + ` + : ''}; +` + +export const SubmitButton = styled(ButtonSolid)` + width: 117.65px; + height: 50px; + font-size: 20px; + font-weight: 600; + + ${isMobileDevice + ? css` + width: 100%; + height: 32px; + font-size: 12px; + ` + : ''}; +` + +export const Text = styled.div` + font-size: 12px; + white-space: nowrap; + color: rgba(255, 255, 255, 0.7); +` + +export const StyledLink = styled(Link)` + color: rgba(255, 255, 255, 0.7); + text-decoration: underline; +` + +export const Error = styled(ErrorBase)` + position: absolute; + left: 0; + bottom: -25px; +` diff --git a/src/features/AuthServiceApp/helpers/getAuthUrl/index.tsx b/src/features/AuthServiceApp/helpers/getAuthUrl/index.tsx index 83cc78cf..f0e8a8d3 100644 --- a/src/features/AuthServiceApp/helpers/getAuthUrl/index.tsx +++ b/src/features/AuthServiceApp/helpers/getAuthUrl/index.tsx @@ -1,5 +1,8 @@ +import { GOOGLE_CLIENT_ID, FACEBOOK_CLIENT_ID } from 'config' + +import { getRandomString } from 'helpers' + import type { Settings } from 'features/AuthStore/helpers' -import { GOOGLE_CLIENT_ID } from 'config' import { AuthProviders } from '../../config/authProviders' import { PAGES } from '../../config/pages' @@ -16,18 +19,32 @@ const getQueryString = (authProvider: AuthProviders, urlParams: Settings) => { state: urlParams.state || '', }).toString() + case AuthProviders.Facebook: + return new URLSearchParams({ + client_id: FACEBOOK_CLIENT_ID, + code_challenge: getRandomString(43), + nonce: urlParams.nonce || '0394852-3190485-2490358', + redirect_uri: `${window.location.origin}${PAGES.oauth}`, + response_type: 'id_token', + scope: 'openid email public_profile', + state: urlParams.state || '', + }).toString() + default: return '' } } export const getAuthUrl = (authProvider: AuthProviders, urlParams: Settings) => { - const queryString = getQueryString(AuthProviders.Google, urlParams) + const queryString = getQueryString(authProvider, urlParams) switch (authProvider) { case AuthProviders.Google: return `https://accounts.google.com/o/oauth2/v2/auth?${queryString}` + case AuthProviders.Facebook: + return `https://www.facebook.com/v11.0/dialog/oauth?${queryString}` + default: return '' } diff --git a/src/helpers/getRandomString/index.tsx b/src/helpers/getRandomString/index.tsx new file mode 100644 index 00000000..c23816b3 --- /dev/null +++ b/src/helpers/getRandomString/index.tsx @@ -0,0 +1,11 @@ +export const getRandomString = (length: number) => { + let result = '' + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + const charactersLength = characters.length + + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)) + } + + return result +} diff --git a/src/helpers/index.tsx b/src/helpers/index.tsx index cb43b81d..06dc4502 100644 --- a/src/helpers/index.tsx +++ b/src/helpers/index.tsx @@ -6,3 +6,4 @@ export * from './getSportLexic' export * from './msToMinutesAndSeconds' export * from './secondsToHms' export * from './redirectToUrl' +export * from './getRandomString'