diff --git a/lib/utils/farcaster-utils.js b/lib/utils/farcaster-utils.js --- a/lib/utils/farcaster-utils.js +++ b/lib/utils/farcaster-utils.js @@ -1,7 +1,12 @@ // @flow +import invariant from 'invariant'; +import * as React from 'react'; + +import { setSyncedMetadataEntryActionType } from '../actions/synced-metadata-actions.js'; +import { IdentityClientContext } from '../shared/identity-client-context.js'; import { syncedMetadataNames } from '../types/synced-metadata-types.js'; -import { useSelector } from '../utils/redux-utils.js'; +import { useSelector, useDispatch } from '../utils/redux-utils.js'; function useCurrentUserFID(): ?string { return useSelector( @@ -12,4 +17,29 @@ ); } -export { useCurrentUserFID }; +function useLinkFID(): (fid: string) => Promise { + const identityClientContext = React.useContext(IdentityClientContext); + invariant(identityClientContext, 'identityClientContext should be set'); + + const { identityClient } = identityClientContext; + const { linkFarcasterAccount } = identityClient; + + const dispatch = useDispatch(); + + return React.useCallback( + async (fid: string) => { + await linkFarcasterAccount(fid); + + dispatch({ + type: setSyncedMetadataEntryActionType, + payload: { + name: syncedMetadataNames.CURRENT_USER_FID, + data: fid, + }, + }); + }, + [dispatch, linkFarcasterAccount], + ); +} + +export { useCurrentUserFID, useLinkFID }; diff --git a/native/components/connect-farcaster-bottom-sheet.react.js b/native/components/connect-farcaster-bottom-sheet.react.js --- a/native/components/connect-farcaster-bottom-sheet.react.js +++ b/native/components/connect-farcaster-bottom-sheet.react.js @@ -5,11 +5,8 @@ import { View, StyleSheet } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { setSyncedMetadataEntryActionType } from 'lib/actions/synced-metadata-actions.js'; import { useIsAppForegrounded } from 'lib/shared/lifecycle-utils.js'; -import { syncedMetadataNames } from 'lib/types/synced-metadata-types.js'; import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js'; -import { useDispatch } from 'lib/utils/redux-utils.js'; import FarcasterPrompt from './farcaster-prompt.react.js'; import FarcasterWebView, { @@ -20,6 +17,7 @@ import BottomSheet from '../bottom-sheet/bottom-sheet.react.js'; import type { RootNavigationProp } from '../navigation/root-navigator.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; +import { useTryLinkFID } from '../utils/farcaster-utils.js'; const bottomSheetPaddingTop = 32; const farcasterPromptHeight = 350; @@ -34,25 +32,30 @@ function ConnectFarcasterBottomSheet(props: Props): React.Node { const { navigation } = props; - const dispatch = useDispatch(); + const { goBack } = navigation; + + const [webViewState, setWebViewState] = + React.useState('closed'); + + const [isLoadingLinkFID, setIsLoadingLinkFID] = React.useState(false); const fid = useCurrentUserFID(); + const tryLinkFID = useTryLinkFID(); + const onSuccess = React.useCallback( - (newFID: string) => { - dispatch({ - type: setSyncedMetadataEntryActionType, - payload: { - name: syncedMetadataNames.CURRENT_USER_FID, - data: newFID, - }, - }); + async (newFID: string) => { + setWebViewState('closed'); + + try { + await tryLinkFID(newFID); + } finally { + setIsLoadingLinkFID(false); + } }, - [dispatch], + [tryLinkFID], ); - const { goBack } = navigation; - const bottomSheetRef = React.useRef(null); const bottomSheetContext = React.useContext(BottomSheetContext); @@ -71,9 +74,6 @@ ); }, [insets.bottom, setContentHeight]); - const [webViewState, setWebViewState] = - React.useState('closed'); - const isAppForegrounded = useIsAppForegrounded(); React.useEffect(() => { @@ -83,11 +83,11 @@ }, [fid, isAppForegrounded]); const onPressConnect = React.useCallback(() => { + setIsLoadingLinkFID(true); setWebViewState('opening'); }, []); - const connectButtonVariant = - webViewState === 'opening' ? 'loading' : 'enabled'; + const connectButtonVariant = isLoadingLinkFID ? 'loading' : 'enabled'; const connectFarcasterBottomSheet = React.useMemo( () => ( diff --git a/native/profile/farcaster-account-settings.react.js b/native/profile/farcaster-account-settings.react.js --- a/native/profile/farcaster-account-settings.react.js +++ b/native/profile/farcaster-account-settings.react.js @@ -3,10 +3,7 @@ import * as React from 'react'; import { View } from 'react-native'; -import { - setSyncedMetadataEntryActionType, - clearSyncedMetadataEntryActionType, -} from 'lib/actions/synced-metadata-actions.js'; +import { clearSyncedMetadataEntryActionType } from 'lib/actions/synced-metadata-actions.js'; import { syncedMetadataNames } from 'lib/types/synced-metadata-types.js'; import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; @@ -18,6 +15,7 @@ import type { FarcasterWebViewState } from '../components/farcaster-web-view.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useStyles } from '../themes/colors.js'; +import { useTryLinkFID } from '../utils/farcaster-utils.js'; type Props = { +navigation: ProfileNavigationProp<'FarcasterAccountSettings'>, @@ -44,26 +42,29 @@ const [webViewState, setWebViewState] = React.useState('closed'); + const [isLoadingLinkFID, setIsLoadingLinkFID] = React.useState(false); + + const tryLinkFID = useTryLinkFID(); + const onSuccess = React.useCallback( - (newFID: string) => { + async (newFID: string) => { setWebViewState('closed'); - dispatch({ - type: setSyncedMetadataEntryActionType, - payload: { - name: syncedMetadataNames.CURRENT_USER_FID, - data: newFID, - }, - }); + + try { + await tryLinkFID(newFID); + } finally { + setIsLoadingLinkFID(false); + } }, - [dispatch], + [tryLinkFID], ); const onPressConnectFarcaster = React.useCallback(() => { + setIsLoadingLinkFID(true); setWebViewState('opening'); }, []); - const connectButtonVariant = - webViewState === 'opening' ? 'loading' : 'enabled'; + const connectButtonVariant = isLoadingLinkFID ? 'loading' : 'enabled'; const button = React.useMemo(() => { if (fid) { diff --git a/native/utils/farcaster-utils.js b/native/utils/farcaster-utils.js new file mode 100644 --- /dev/null +++ b/native/utils/farcaster-utils.js @@ -0,0 +1,42 @@ +// @flow + +import * as React from 'react'; +import { Alert } from 'react-native'; + +import { getMessageForException } from 'lib/utils/errors.js'; +import { useLinkFID } from 'lib/utils/farcaster-utils.js'; + +import { + getFarcasterAccountAlreadyLinkedAlertDetails, + UnknownErrorAlertDetails, +} from './alert-messages.js'; + +function useTryLinkFID(): (newFid: string) => Promise { + const linkFID = useLinkFID(); + + return React.useCallback( + async (newFID: string) => { + try { + await linkFID(newFID); + } catch (e) { + if ( + getMessageForException(e) === + 'farcaster ID already associated with different user' + ) { + const { title, message } = + getFarcasterAccountAlreadyLinkedAlertDetails(); + + Alert.alert(title, message); + } else { + Alert.alert( + UnknownErrorAlertDetails.title, + UnknownErrorAlertDetails.message, + ); + } + } + }, + [linkFID], + ); +} + +export { useTryLinkFID };