diff --git a/lib/utils/farcaster-utils.js b/lib/utils/farcaster-utils.js index ab7b21bf4..973d01d77 100644 --- a/lib/utils/farcaster-utils.js +++ b/lib/utils/farcaster-utils.js @@ -1,45 +1,69 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; -import { setSyncedMetadataEntryActionType } from '../actions/synced-metadata-actions.js'; +import { + setSyncedMetadataEntryActionType, + clearSyncedMetadataEntryActionType, +} from '../actions/synced-metadata-actions.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { syncedMetadataNames } from '../types/synced-metadata-types.js'; import { useSelector, useDispatch } from '../utils/redux-utils.js'; function useCurrentUserFID(): ?string { return useSelector( state => state.syncedMetadataStore.syncedMetadata[ syncedMetadataNames.CURRENT_USER_FID ] ?? null, ); } 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 }; +function useUnlinkFID(): () => Promise { + const identityClientContext = React.useContext(IdentityClientContext); + invariant(identityClientContext, 'identityClientContext should be set'); + + const { identityClient } = identityClientContext; + const { unlinkFarcasterAccount } = identityClient; + + const dispatch = useDispatch(); + + return React.useCallback(async () => { + await unlinkFarcasterAccount(); + + dispatch({ + type: clearSyncedMetadataEntryActionType, + payload: { + name: syncedMetadataNames.CURRENT_USER_FID, + }, + }); + }, [dispatch, unlinkFarcasterAccount]); +} + +export { useCurrentUserFID, useLinkFID, useUnlinkFID }; diff --git a/native/profile/farcaster-account-settings.react.js b/native/profile/farcaster-account-settings.react.js index e0809877a..1a507b5d1 100644 --- a/native/profile/farcaster-account-settings.react.js +++ b/native/profile/farcaster-account-settings.react.js @@ -1,131 +1,144 @@ // @flow import * as React from 'react'; -import { View } from 'react-native'; +import { View, Alert } from 'react-native'; -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'; +import { useCurrentUserFID, useUnlinkFID } from 'lib/utils/farcaster-utils.js'; import type { ProfileNavigationProp } from './profile.react.js'; import RegistrationButton from '../account/registration/registration-button.react.js'; import FarcasterPrompt from '../components/farcaster-prompt.react.js'; import FarcasterWebView from '../components/farcaster-web-view.react.js'; 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 { UnknownErrorAlertDetails } from '../utils/alert-messages.js'; import { useTryLinkFID } from '../utils/farcaster-utils.js'; type Props = { +navigation: ProfileNavigationProp<'FarcasterAccountSettings'>, +route: NavigationRoute<'FarcasterAccountSettings'>, }; // eslint-disable-next-line no-unused-vars function FarcasterAccountSettings(props: Props): React.Node { - const dispatch = useDispatch(); - const fid = useCurrentUserFID(); const styles = useStyles(unboundStyles); - const onPressDisconnect = React.useCallback(() => { - dispatch({ - type: clearSyncedMetadataEntryActionType, - payload: { - name: syncedMetadataNames.CURRENT_USER_FID, - }, - }); - }, [dispatch]); + const [isLoadingUnlinkFID, setIsLoadingUnlinkFID] = React.useState(false); + + const unlinkFID = useUnlinkFID(); + + const onPressDisconnect = React.useCallback(async () => { + setIsLoadingUnlinkFID(true); + try { + await unlinkFID(); + } catch { + Alert.alert( + UnknownErrorAlertDetails.title, + UnknownErrorAlertDetails.message, + ); + } finally { + setIsLoadingUnlinkFID(false); + } + }, [unlinkFID]); const [webViewState, setWebViewState] = React.useState('closed'); const [isLoadingLinkFID, setIsLoadingLinkFID] = React.useState(false); const tryLinkFID = useTryLinkFID(); const onSuccess = React.useCallback( async (newFID: string) => { setWebViewState('closed'); try { await tryLinkFID(newFID); } finally { setIsLoadingLinkFID(false); } }, [tryLinkFID], ); const onPressConnectFarcaster = React.useCallback(() => { setIsLoadingLinkFID(true); setWebViewState('opening'); }, []); + const disconnectButtonVariant = isLoadingUnlinkFID ? 'loading' : 'outline'; + const connectButtonVariant = isLoadingLinkFID ? 'loading' : 'enabled'; const button = React.useMemo(() => { if (fid) { return ( ); } return ( ); - }, [connectButtonVariant, fid, onPressConnectFarcaster, onPressDisconnect]); + }, [ + connectButtonVariant, + disconnectButtonVariant, + fid, + onPressConnectFarcaster, + onPressDisconnect, + ]); const farcasterPromptTextType = fid ? 'disconnect' : 'optional'; const farcasterAccountSettings = React.useMemo( () => ( {button} ), [ button, farcasterPromptTextType, onSuccess, styles.buttonContainer, styles.connectContainer, styles.promptContainer, webViewState, ], ); return farcasterAccountSettings; } const unboundStyles = { connectContainer: { flex: 1, backgroundColor: 'panelBackground', paddingBottom: 16, }, promptContainer: { flex: 1, padding: 16, justifyContent: 'space-between', }, buttonContainer: { marginVertical: 8, marginHorizontal: 16, }, }; export default FarcasterAccountSettings;