Skip to content

Commit

Permalink
Merge pull request #33404 from shahinyan11/issue/31980
Browse files Browse the repository at this point in the history
[TS migration] Migrate 'SignInButtons' component to TypeScript
  • Loading branch information
yuwenmemon authored Jan 11, 2024
2 parents 55432ca + cdea74d commit 8460043
Show file tree
Hide file tree
Showing 17 changed files with 167 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@ import * as Session from '@userActions/Session';
/**
* Apple Sign In wrapper for iOS
* revokes the session if the credential is revoked.
*
* @returns {null}
*/
function AppleAuthWrapper() {
useEffect(() => {
if (!appleAuth.isSupported) {
return;
}
const listener = appleAuth.onCredentialRevoked(() => {
const removeListener = appleAuth.onCredentialRevoked(() => {
Session.signOut();
});

return () => {
listener.remove();
removeListener();
};
}, []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ const config = {

/**
* Apple Sign In method for Android that returns authToken.
* @returns {Promise<string>}
* @returns Promise that returns a string when resolved
*/
function appleSignInRequest() {
function appleSignInRequest(): Promise<string | undefined> {
appleAuthAndroid.configure(config);
return appleAuthAndroid
.signIn()
Expand All @@ -32,7 +32,6 @@ function appleSignInRequest() {

/**
* Apple Sign In button for Android.
* @returns {React.Component}
*/
function AppleSignIn() {
const handleSignIn = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const appleSignInWebRouteForDesktopFlow = `${CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL}

/**
* Apple Sign In button for desktop flow
* @returns {React.Component}
*/
function AppleSignIn() {
const styles = useThemeStyles();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import CONST from '@src/CONST';

/**
* Apple Sign In method for iOS that returns identityToken.
* @returns {Promise<string>}
* @returns Promise that returns a string when resolved
*/
function appleSignInRequest() {
function appleSignInRequest(): Promise<string | null | undefined> {
return appleAuth
.performRequest({
requestedOperation: appleAuth.Operation.LOGIN,
Expand All @@ -20,7 +20,7 @@ function appleSignInRequest() {
.then((response) =>
appleAuth.getCredentialStateForUser(response.user).then((credentialState) => {
if (credentialState !== appleAuth.State.AUTHORIZED) {
Log.alert('[Apple Sign In] Authentication failed. Original response: ', response);
Log.alert('[Apple Sign In] Authentication failed. Original response: ', {response});
throw new Error('Authentication failed');
}
return response.identityToken;
Expand All @@ -30,7 +30,6 @@ function appleSignInRequest() {

/**
* Apple Sign In button for iOS.
* @returns {React.Component}
*/
function AppleSignIn() {
const handleSignIn = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,37 @@
import get from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useEffect, useState} from 'react';
import Config from 'react-native-config';
import Config, {NativeConfig} from 'react-native-config';

Check failure on line 2 in src/components/SignInButtons/AppleSignIn/index.website.tsx

View workflow job for this annotation

GitHub Actions / lint / lint

Import "NativeConfig" is only used as types
import getUserLanguage from '@components/SignInButtons/GetUserLanguage';
import withNavigationFocus from '@components/withNavigationFocus';
import withNavigationFocus, {WithNavigationFocusProps} from '@components/withNavigationFocus';

Check failure on line 4 in src/components/SignInButtons/AppleSignIn/index.website.tsx

View workflow job for this annotation

GitHub Actions / lint / lint

Import "WithNavigationFocusProps" is only used as types
import Log from '@libs/Log';
import * as Session from '@userActions/Session';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import {AppleIDSignInOnFailureEvent, AppleIDSignInOnSuccessEvent} from '@src/types/modules/dom';

Check failure on line 9 in src/components/SignInButtons/AppleSignIn/index.website.tsx

View workflow job for this annotation

GitHub Actions / lint / lint

All imports in the declaration are only used as types. Use `import type`

// react-native-config doesn't trim whitespace on iOS for some reason so we
// add a trim() call to lodashGet here to prevent headaches.
const lodashGet = (config, key, defaultValue) => get(config, key, defaultValue).trim();
const getConfig = (config: NativeConfig, key: string, defaultValue: string) => (config?.[key] ?? defaultValue).trim();

const requiredPropTypes = {
isDesktopFlow: PropTypes.bool.isRequired,
type AppleSignInDivProps = {
isDesktopFlow: boolean;
};

const singletonPropTypes = {
...requiredPropTypes,

// From withNavigationFocus
isFocused: PropTypes.bool.isRequired,
type SingletonAppleSignInButtonProps = AppleSignInDivProps & {
isFocused: boolean;
};

const propTypes = {
// Prop to indicate if this is the desktop flow or not.
isDesktopFlow: PropTypes.bool,
};
const defaultProps = {
isDesktopFlow: false,
type AppleSignInProps = WithNavigationFocusProps & {
isDesktopFlow?: boolean;
};

/**
* Apple Sign In Configuration for Web.
*/
const config = {
clientId: lodashGet(Config, 'ASI_CLIENTID_OVERRIDE', CONFIG.APPLE_SIGN_IN.SERVICE_ID),
clientId: getConfig(Config, 'ASI_CLIENTID_OVERRIDE', CONFIG.APPLE_SIGN_IN.SERVICE_ID),
scope: 'name email',
// never used, but required for configuration
redirectURI: lodashGet(Config, 'ASI_REDIRECTURI_OVERRIDE', CONFIG.APPLE_SIGN_IN.REDIRECT_URI),
redirectURI: getConfig(Config, 'ASI_REDIRECTURI_OVERRIDE', CONFIG.APPLE_SIGN_IN.REDIRECT_URI),
state: '',
nonce: '',
usePopup: true,
Expand All @@ -49,23 +41,22 @@ const config = {
* Apple Sign In success and failure listeners.
*/

const successListener = (event) => {
const successListener = (event: AppleIDSignInOnSuccessEvent) => {
const token = event.detail.authorization.id_token;
Session.beginAppleSignIn(token);
};

const failureListener = (event) => {
const failureListener = (event: AppleIDSignInOnFailureEvent) => {
if (!event.detail || event.detail.error === 'popup_closed_by_user') {
return null;
}
Log.warn(`Apple sign-in failed: ${event.detail}`);
Log.warn(`Apple sign-in failed: ${event.detail.error}`);
};

/**
* Apple Sign In button for Web.
* @returns {React.Component}
*/
function AppleSignInDiv({isDesktopFlow}) {
function AppleSignInDiv({isDesktopFlow}: AppleSignInDivProps) {
useEffect(() => {
// `init` renders the button, so it must be called after the div is
// first mounted.
Expand Down Expand Up @@ -108,24 +99,20 @@ function AppleSignInDiv({isDesktopFlow}) {
);
}

AppleSignInDiv.propTypes = requiredPropTypes;

// The Sign in with Apple script may fail to render button if there are multiple
// of these divs present in the app, as it matches based on div id. So we'll
// only mount the div when it should be visible.
function SingletonAppleSignInButton({isFocused, isDesktopFlow}) {
function SingletonAppleSignInButton({isFocused, isDesktopFlow}: SingletonAppleSignInButtonProps) {
if (!isFocused) {
return null;
}
return <AppleSignInDiv isDesktopFlow={isDesktopFlow} />;
}

SingletonAppleSignInButton.propTypes = singletonPropTypes;

// withNavigationFocus is used to only render the button when it is visible.
const SingletonAppleSignInButtonWithFocus = withNavigationFocus(SingletonAppleSignInButton);

function AppleSignIn({isDesktopFlow}) {
function AppleSignIn({isDesktopFlow = false}: AppleSignInProps) {
const [scriptLoaded, setScriptLoaded] = useState(false);
useEffect(() => {
if (window.appleAuthScriptLoaded) {
Expand All @@ -148,7 +135,5 @@ function AppleSignIn({isDesktopFlow}) {
return <SingletonAppleSignInButtonWithFocus isDesktopFlow={isDesktopFlow} />;
}

AppleSignIn.propTypes = propTypes;
AppleSignIn.defaultProps = defaultProps;

AppleSignIn.displayName = 'AppleSignIn';
export default withNavigationFocus(AppleSignIn);
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import {ValueOf} from 'type-fest';

Check failure on line 1 in src/components/SignInButtons/GetUserLanguage.ts

View workflow job for this annotation

GitHub Actions / lint / lint

All imports in the declaration are only used as types. Use `import type`

const localeCodes = {
en: 'en_US',
es: 'es_ES',
};
} as const;

type LanguageCode = keyof typeof localeCodes;
type LocaleCode = ValueOf<typeof localeCodes>;

const GetUserLanguage = () => {
const GetUserLanguage = (): LocaleCode => {
const userLanguage = navigator.language || navigator.userLanguage;
const languageCode = userLanguage.split('-')[0];
const languageCode = userLanguage.split('-')[0] as LanguageCode;
return localeCodes[languageCode] || 'en_US';
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import React from 'react';
import {View} from 'react-native';
import IconButton from '@components/SignInButtons/IconButton';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';

const propTypes = {...withLocalizePropTypes};

const googleSignInWebRouteForDesktopFlow = `${CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL}${ROUTES.GOOGLE_SIGN_IN}`;

/**
* Google Sign In button for desktop flow.
* @returns {React.Component}
*/
function GoogleSignIn() {
const styles = useThemeStyles();
Expand All @@ -30,6 +26,5 @@ function GoogleSignIn() {
}

GoogleSignIn.displayName = 'GoogleSignIn';
GoogleSignIn.propTypes = propTypes;

export default withLocalize(GoogleSignIn);
export default GoogleSignIn;
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ function googleSignInRequest() {

/**
* Google Sign In button for iOS.
* @returns {React.Component}
*/
function GoogleSignIn() {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
import PropTypes from 'prop-types';
import React, {useCallback} from 'react';
import {View} from 'react-native';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Session from '@userActions/Session';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import Response from '@src/types/modules/google';

Check failure on line 8 in src/components/SignInButtons/GoogleSignIn/index.website.tsx

View workflow job for this annotation

GitHub Actions / lint / lint

All imports in the declaration are only used as types. Use `import type`

const propTypes = {
/** Whether we're rendering in the Desktop Flow, if so show a different button. */
isDesktopFlow: PropTypes.bool,

...withLocalizePropTypes,
};

const defaultProps = {
isDesktopFlow: false,
type GoogleSignInProps = {
isDesktopFlow?: boolean;
};

/** Div IDs for styling the two different Google Sign-In buttons. */
const mainId = 'google-sign-in-main';
const desktopId = 'google-sign-in-desktop';

const signIn = (response) => {
const signIn = (response: Response) => {
Session.beginGoogleSignIn(response.credential);
};

Expand All @@ -31,12 +24,15 @@ const signIn = (response) => {
* We have to load the gis script and then determine if the page is focused before rendering the button.
* @returns {React.Component}
*/
function GoogleSignIn({translate, isDesktopFlow}) {

function GoogleSignIn({isDesktopFlow = false}: GoogleSignInProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const loadScript = useCallback(() => {
const google = window.google;
if (google) {
google.accounts.id.initialize({
// eslint-disable-next-line @typescript-eslint/naming-convention
client_id: CONFIG.GOOGLE_SIGN_IN.WEB_CLIENT_ID,
callback: signIn,
});
Expand Down Expand Up @@ -92,7 +88,5 @@ function GoogleSignIn({translate, isDesktopFlow}) {
}

GoogleSignIn.displayName = 'GoogleSignIn';
GoogleSignIn.propTypes = propTypes;
GoogleSignIn.defaultProps = defaultProps;

export default withLocalize(GoogleSignIn);
export default GoogleSignIn;
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
import PropTypes from 'prop-types';
import React from 'react';
import {ValueOf} from 'type-fest';

Check failure on line 2 in src/components/SignInButtons/IconButton.tsx

View workflow job for this annotation

GitHub Actions / lint / lint

All imports in the declaration are only used as types. Use `import type`
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';

const propTypes = {
/** The on press method */
onPress: PropTypes.func,

/** Which provider you are using to sign in */
provider: PropTypes.string.isRequired,

...withLocalizePropTypes,
};

const defaultProps = {
onPress: () => {},
};
import {TranslationPaths} from '@src/languages/types';

Check failure on line 9 in src/components/SignInButtons/IconButton.tsx

View workflow job for this annotation

GitHub Actions / lint / lint

All imports in the declaration are only used as types. Use `import type`
import IconAsset from '@src/types/utils/IconAsset';

Check failure on line 10 in src/components/SignInButtons/IconButton.tsx

View workflow job for this annotation

GitHub Actions / lint / lint

All imports in the declaration are only used as types. Use `import type`

const providerData = {
[CONST.SIGN_IN_METHOD.APPLE]: {
Expand All @@ -30,9 +18,21 @@ const providerData = {
icon: Expensicons.GoogleLogo,
accessibilityLabel: 'common.signInWithGoogle',
},
} satisfies Record<
ValueOf<typeof CONST.SIGN_IN_METHOD>,
{
icon: IconAsset;
accessibilityLabel: TranslationPaths;
}
>;

type IconButtonProps = {
onPress?: () => void;
provider: ValueOf<typeof CONST.SIGN_IN_METHOD>;
};

function IconButton({onPress, translate, provider}) {
function IconButton({onPress = () => {}, provider}: IconButtonProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
return (
<PressableWithoutFeedback
Expand All @@ -51,7 +51,5 @@ function IconButton({onPress, translate, provider}) {
}

IconButton.displayName = 'IconButton';
IconButton.propTypes = propTypes;
IconButton.defaultProps = defaultProps;

export default withLocalize(IconButton);
export default IconButton;
2 changes: 2 additions & 0 deletions src/components/withNavigationFocus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ export default function withNavigationFocus<TProps extends WithNavigationFocusPr
WithNavigationFocus.displayName = `withNavigationFocus(${getComponentDisplayName(WrappedComponent)})`;
return React.forwardRef(WithNavigationFocus);
}

export type {WithNavigationFocusProps};
Loading

0 comments on commit 8460043

Please sign in to comment.