Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TS migration] Migrate 'SignInButtons' component to TypeScript #33404

Merged
merged 5 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
shahinyan11 marked this conversation as resolved.
Show resolved Hide resolved
};
}, []);

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> {
shahinyan11 marked this conversation as resolved.
Show resolved Hide resolved
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';
import getUserLanguage from '@components/SignInButtons/GetUserLanguage';
import withNavigationFocus from '@components/withNavigationFocus';
import withNavigationFocus, {WithNavigationFocusProps} from '@components/withNavigationFocus';
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';

// 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}`);
};
shahinyan11 marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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';

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
shahinyan11 marked this conversation as resolved.
Show resolved Hide resolved
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';

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';
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';
import IconAsset from '@src/types/utils/IconAsset';

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 @@ -24,3 +24,5 @@ export default function withNavigationFocus<TProps extends WithNavigationFocusPr
WithNavigationFocus.displayName = `withNavigationFocus(${getComponentDisplayName(WrappedComponent)})`;
return React.forwardRef(WithNavigationFocus);
}

export type {WithNavigationFocusProps};
Loading
Loading