diff --git a/native/account/registration/connect-farcaster.react.js b/native/account/registration/connect-farcaster.react.js index f5221c6ac..716c28a56 100644 --- a/native/account/registration/connect-farcaster.react.js +++ b/native/account/registration/connect-farcaster.react.js @@ -1,259 +1,246 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; import { useIsAppForegrounded } from 'lib/shared/lifecycle-utils.js'; import { siweNonceExpired } from './ethereum-utils.js'; import RegistrationButtonContainer from './registration-button-container.react.js'; import RegistrationContainer from './registration-container.react.js'; import RegistrationContentContainer from './registration-content-container.react.js'; import { RegistrationContext } from './registration-context.js'; import type { RegistrationNavigationProp } from './registration-navigator.react.js'; import type { CoolOrNerdMode } from './registration-types.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 PrimaryButton from '../../components/primary-button.react.js'; import { type NavigationRoute, ConnectEthereumRouteName, AvatarSelectionRouteName, } from '../../navigation/route-names.js'; import { getFarcasterAccountAlreadyLinkedAlertDetails, type AlertDetails, } from '../../utils/alert-messages.js'; import Alert from '../../utils/alert.js'; -import { useStaffCanSee } from '../../utils/staff-utils.js'; export type ConnectFarcasterParams = ?{ +userSelections?: { +coolOrNerdMode?: CoolOrNerdMode, +keyserverURL?: string, }, }; type Props = { +navigation: RegistrationNavigationProp<'ConnectFarcaster'>, +route: NavigationRoute<'ConnectFarcaster'>, }; function ConnectFarcaster(prop: Props): React.Node { const { navigation, route } = prop; const { navigate } = navigation; const userSelections = route.params?.userSelections; const registrationContext = React.useContext(RegistrationContext); invariant(registrationContext, 'registrationContext should be set'); const { cachedSelections, setCachedSelections, skipEthereumLoginOnce, setSkipEthereumLoginOnce, } = registrationContext; const [webViewState, setWebViewState] = React.useState('closed'); const { ethereumAccount } = cachedSelections; const goToNextStep = React.useCallback( (fid?: ?string) => { setWebViewState('closed'); invariant( !ethereumAccount || ethereumAccount.nonceTimestamp, 'nonceTimestamp must be set after connecting to Ethereum account', ); const nonceExpired = ethereumAccount && ethereumAccount.nonceTimestamp && siweNonceExpired(ethereumAccount.nonceTimestamp); if (nonceExpired) { setCachedSelections(oldUserSelections => ({ ...oldUserSelections, ethereumAccount: undefined, })); } if (!skipEthereumLoginOnce || !ethereumAccount || nonceExpired) { navigate<'ConnectEthereum'>({ name: ConnectEthereumRouteName, params: { userSelections: { ...userSelections, farcasterID: fid, }, }, }); return; } const newUserSelections = { ...userSelections, farcasterID: fid, accountSelection: ethereumAccount, }; setSkipEthereumLoginOnce(false); navigate<'AvatarSelection'>({ name: AvatarSelectionRouteName, params: { userSelections: newUserSelections }, }); }, [ navigate, skipEthereumLoginOnce, setSkipEthereumLoginOnce, ethereumAccount, userSelections, setCachedSelections, ], ); const onSkip = React.useCallback(() => goToNextStep(), [goToNextStep]); const identityServiceClient = React.useContext(IdentityClientContext); const getFarcasterUsers = identityServiceClient?.identityClient.getFarcasterUsers; invariant(getFarcasterUsers, 'Could not get getFarcasterUsers'); const [queuedAlert, setQueuedAlert] = React.useState(); const onSuccess = React.useCallback( async (fid: string) => { try { const commFCUsers = await getFarcasterUsers([fid]); if (commFCUsers.length > 0 && commFCUsers[0].farcasterID === fid) { const commUsername = commFCUsers[0].username; const alert = getFarcasterAccountAlreadyLinkedAlertDetails(commUsername); setQueuedAlert(alert); setWebViewState('closed'); } else { goToNextStep(fid); setCachedSelections(oldUserSelections => ({ ...oldUserSelections, farcasterID: fid, })); } } catch (e) { setQueuedAlert({ title: 'Failed to query Comm', message: 'We failed to query Comm to see if that Farcaster account is ' + 'already linked', }); setWebViewState('closed'); } }, [goToNextStep, setCachedSelections, getFarcasterUsers], ); const isAppForegrounded = useIsAppForegrounded(); React.useEffect(() => { if (!queuedAlert || !isAppForegrounded) { return; } Alert.alert(queuedAlert.title, queuedAlert.message); setQueuedAlert(null); }, [queuedAlert, isAppForegrounded]); const { farcasterID } = cachedSelections; const alreadyHasConnected = !!farcasterID; const onPressConnectFarcaster = React.useCallback(() => { setWebViewState('opening'); }, []); const defaultConnectButtonVariant = alreadyHasConnected ? 'outline' : 'enabled'; const connectButtonVariant = webViewState === 'opening' ? 'loading' : defaultConnectButtonVariant; const connectButtonText = alreadyHasConnected ? 'Connect new Farcaster account' : 'Connect Farcaster account'; const onUseAlreadyConnectedAccount = React.useCallback(() => { invariant( farcasterID, 'farcasterID should be set in onUseAlreadyConnectedAccount', ); goToNextStep(farcasterID); }, [farcasterID, goToNextStep]); const alreadyConnectedButton = React.useMemo(() => { if (!alreadyHasConnected) { return null; } return ( ); }, [alreadyHasConnected, onUseAlreadyConnectedAccount]); - const staffCanSee = useStaffCanSee(); - const skipButton = React.useMemo(() => { - if (!staffCanSee) { - return undefined; - } - return ( - - ); - }, [staffCanSee, onSkip]); - - const farcasterPromptTextType = staffCanSee ? 'optional' : 'required'; const connectFarcaster = React.useMemo( () => ( - + {alreadyConnectedButton} - {skipButton} + ), [ alreadyConnectedButton, connectButtonText, connectButtonVariant, onPressConnectFarcaster, onSuccess, webViewState, - farcasterPromptTextType, - skipButton, + onSkip, ], ); return connectFarcaster; } const styles = { scrollViewContentContainer: { flexGrow: 1, }, }; export default ConnectFarcaster; diff --git a/native/components/connect-farcaster-bottom-sheet.react.js b/native/components/connect-farcaster-bottom-sheet.react.js index 2e24f3b32..3546bf1da 100644 --- a/native/components/connect-farcaster-bottom-sheet.react.js +++ b/native/components/connect-farcaster-bottom-sheet.react.js @@ -1,124 +1,124 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { View, StyleSheet } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useIsAppForegrounded } from 'lib/shared/lifecycle-utils.js'; import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js'; import FarcasterPrompt from './farcaster-prompt.react.js'; import FarcasterWebView, { type FarcasterWebViewState, } from './farcaster-web-view.react.js'; import PrimaryButton from './primary-button.react.js'; import { BottomSheetContext } from '../bottom-sheet/bottom-sheet-provider.react.js'; 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; const marginBottom = 40; const buttonHeight = 48; type Props = { +navigation: RootNavigationProp<'ConnectFarcasterBottomSheet'>, +route: NavigationRoute<'ConnectFarcasterBottomSheet'>, }; function ConnectFarcasterBottomSheet(props: Props): React.Node { const { navigation } = props; 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( async (newFID: string) => { setWebViewState('closed'); try { await tryLinkFID(newFID); } finally { setIsLoadingLinkFID(false); } }, [tryLinkFID], ); const bottomSheetRef = React.useRef(null); const bottomSheetContext = React.useContext(BottomSheetContext); invariant(bottomSheetContext, 'bottomSheetContext should be set'); const { setContentHeight } = bottomSheetContext; const insets = useSafeAreaInsets(); React.useLayoutEffect(() => { setContentHeight( bottomSheetPaddingTop + farcasterPromptHeight + marginBottom + buttonHeight + insets.bottom, ); }, [insets.bottom, setContentHeight]); const isAppForegrounded = useIsAppForegrounded(); React.useEffect(() => { if (fid && isAppForegrounded) { bottomSheetRef.current?.close(); } }, [fid, isAppForegrounded]); const onPressConnect = React.useCallback(() => { setIsLoadingLinkFID(true); setWebViewState('opening'); }, []); const connectButtonVariant = isLoadingLinkFID ? 'loading' : 'enabled'; const connectFarcasterBottomSheet = React.useMemo( () => ( - + ), [connectButtonVariant, goBack, onPressConnect, onSuccess, webViewState], ); return connectFarcasterBottomSheet; } const styles = StyleSheet.create({ container: { flex: 1, paddingHorizontal: 16, }, promptContainer: { marginBottom: 40, }, }); export default ConnectFarcasterBottomSheet; diff --git a/native/components/farcaster-prompt.react.js b/native/components/farcaster-prompt.react.js index daabef0aa..853f2bbe8 100644 --- a/native/components/farcaster-prompt.react.js +++ b/native/components/farcaster-prompt.react.js @@ -1,85 +1,78 @@ // @flow import * as React from 'react'; import { View, Text } from 'react-native'; import { useStyles } from '../themes/colors.js'; import FarcasterLogo from '../vectors/farcaster-logo.react.js'; -type TextType = 'required' | 'optional' | 'disconnect'; +type TextType = 'connect' | 'disconnect'; type Props = { +textType: TextType, }; function FarcasterPrompt(props: Props): React.Node { const { textType } = props; let headerText; - if (textType === 'required') { - headerText = 'Connect your Farcaster account'; - } else if (textType === 'disconnect') { + if (textType === 'disconnect') { headerText = 'Disconnect from Farcaster'; } else { headerText = 'Do you want to connect your Farcaster account?'; } let bodyText; - if (textType === 'required') { - bodyText = - 'Connecting a Farcaster account is currently required. We use it to ' + - 'boostrap your social graph, and to surface communities based on your ' + - 'Farcaster channels.'; - } else if (textType === 'disconnect') { + if (textType === 'disconnect') { bodyText = 'You can disconnect your Farcaster account at any time.'; } else { bodyText = 'Connecting your Farcaster account lets us bootstrap your social ' + 'graph. We’ll also surface communities based on your Farcaster ' + 'channels.'; } const styles = useStyles(unboundStyles); const farcasterPrompt = React.useMemo( () => ( <> {headerText} {bodyText} ), [ bodyText, headerText, styles.body, styles.farcasterLogoContainer, styles.header, ], ); return farcasterPrompt; } const unboundStyles = { header: { fontSize: 24, color: 'panelForegroundLabel', paddingBottom: 16, }, body: { fontFamily: 'Arial', fontSize: 15, lineHeight: 20, color: 'panelForegroundSecondaryLabel', paddingBottom: 16, }, farcasterLogoContainer: { flexGrow: 1, alignItems: 'center', justifyContent: 'center', }, }; export default FarcasterPrompt; diff --git a/native/profile/farcaster-account-settings.react.js b/native/profile/farcaster-account-settings.react.js index 71144d4c9..41f55f7a1 100644 --- a/native/profile/farcaster-account-settings.react.js +++ b/native/profile/farcaster-account-settings.react.js @@ -1,145 +1,145 @@ // @flow import * as React from 'react'; import { View } from 'react-native'; import { useCurrentUserFID, useUnlinkFID } from 'lib/utils/farcaster-utils.js'; import type { ProfileNavigationProp } from './profile.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 PrimaryButton from '../components/primary-button.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { useStyles } from '../themes/colors.js'; import { unknownErrorAlertDetails } from '../utils/alert-messages.js'; import Alert from '../utils/alert.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 fid = useCurrentUserFID(); const styles = useStyles(unboundStyles); 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, disconnectButtonVariant, fid, onPressConnectFarcaster, onPressDisconnect, ]); - const farcasterPromptTextType = fid ? 'disconnect' : 'optional'; + const farcasterPromptTextType = fid ? 'disconnect' : 'connect'; 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;