diff --git a/native/account/registration/connect-farcaster.react.js b/native/account/registration/connect-farcaster.react.js index 12c2a504e..ac4ecda52 100644 --- a/native/account/registration/connect-farcaster.react.js +++ b/native/account/registration/connect-farcaster.react.js @@ -1,254 +1,254 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; -import { Alert } from 'react-native'; 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 RegistrationButton from './registration-button.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 { 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'); const nonceExpired = ethereumAccount && 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, ], ); return connectFarcaster; } const styles = { scrollViewContentContainer: { flexGrow: 1, }, }; export default ConnectFarcaster; diff --git a/native/account/registration/missing-registration-data/missing-siwe-backup-message.react.js b/native/account/registration/missing-registration-data/missing-siwe-backup-message.react.js index d49bf41be..87431d270 100644 --- a/native/account/registration/missing-registration-data/missing-siwe-backup-message.react.js +++ b/native/account/registration/missing-registration-data/missing-siwe-backup-message.react.js @@ -1,74 +1,74 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; -import { Alert } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { type SIWEResult } from 'lib/types/siwe-types.js'; import { isValidEthereumAddress } from 'lib/utils/siwe-utils.js'; import { commCoreModule } from '../../../native-modules.js'; import { type RootNavigationProp } from '../../../navigation/root-navigator.react.js'; import { type NavigationRoute } from '../../../navigation/route-names.js'; import { useSelector } from '../../../redux/redux-utils.js'; import { useStyles } from '../../../themes/colors.js'; +import Alert from '../../../utils/alert.js'; import { CreateSIWEBackupMessageBase } from '../siwe-backup-message-creation.react.js'; type Props = { +navigation: RootNavigationProp<'CreateMissingSIWEBackupMessage'>, +route: NavigationRoute<'CreateMissingSIWEBackupMessage'>, }; function CreateMissingSIWEBackupMessage(props: Props): React.Node { const styles = useStyles(unboundStyles); const { goBack } = props.navigation; const currentUserInfo = useSelector(state => state.currentUserInfo); const loggedInEthereumAccountAddress = currentUserInfo?.username; invariant( loggedInEthereumAccountAddress && isValidEthereumAddress(loggedInEthereumAccountAddress), 'current username must be valid ethereum address to attempt ' + 'backup message generation', ); const onSuccessfulWalletSignature = React.useCallback( (result: SIWEResult) => { void (async () => { const { message, signature, address } = result; if (loggedInEthereumAccountAddress !== address) { Alert.alert( 'Mismatched Ethereum address', 'You picked a different wallet than the one you use to sign in.', ); return; } await commCoreModule.setSIWEBackupSecrets({ message, signature }); goBack(); })(); }, [goBack, loggedInEthereumAccountAddress], ); return ( ); } const safeAreaEdges = ['top']; const unboundStyles = { container: { flex: 1, backgroundColor: 'panelBackground', justifyContent: 'space-between', }, }; export default CreateMissingSIWEBackupMessage; diff --git a/native/account/registration/registration-terms.react.js b/native/account/registration/registration-terms.react.js index 2a72180ac..bbd8da161 100644 --- a/native/account/registration/registration-terms.react.js +++ b/native/account/registration/registration-terms.react.js @@ -1,192 +1,193 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; -import { Text, View, Image, Linking, Alert } from 'react-native'; +import { Text, View, Image, Linking } from 'react-native'; import type { SIWEBackupSecrets } from 'lib/types/siwe-types.js'; import RegistrationButtonContainer from './registration-button-container.react.js'; import RegistrationButton from './registration-button.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, AccountSelection, AvatarData, } from './registration-types.js'; import commSwooshSource from '../../img/comm-swoosh.png'; import { logInActionType } from '../../navigation/action-types.js'; import type { NavigationRoute } from '../../navigation/route-names.js'; import { useStyles } from '../../themes/colors.js'; +import Alert from '../../utils/alert.js'; export type RegistrationTermsParams = { +userSelections: { +coolOrNerdMode?: ?CoolOrNerdMode, +keyserverURL?: ?string, +farcasterID: ?string, +accountSelection: AccountSelection, +avatarData: ?AvatarData, +siweBackupSecrets?: ?SIWEBackupSecrets, }, }; const onTermsOfUsePressed = () => { void Linking.openURL('https://comm.app/terms'); }; const onPrivacyPolicyPressed = () => { void Linking.openURL('https://comm.app/privacy'); }; type Props = { +navigation: RegistrationNavigationProp<'RegistrationTerms'>, +route: NavigationRoute<'RegistrationTerms'>, }; function RegistrationTerms(props: Props): React.Node { const registrationContext = React.useContext(RegistrationContext); invariant(registrationContext, 'registrationContext should be set'); const { register, setCachedSelections } = registrationContext; const [registrationInProgress, setRegistrationInProgress] = React.useState(false); const { userSelections } = props.route.params; const clearCachedSelections = React.useCallback(() => { setCachedSelections({}); }, [setCachedSelections]); const { navigation } = props; const { reconnectEthereum } = navigation; const { coolOrNerdMode, keyserverURL, farcasterID } = userSelections; const navigateToConnectEthereum = React.useCallback(() => { reconnectEthereum({ userSelections: { coolOrNerdMode, keyserverURL, farcasterID, }, }); }, [reconnectEthereum, coolOrNerdMode, keyserverURL, farcasterID]); const onNonceExpired = React.useCallback(() => { setCachedSelections(oldUserSelections => ({ ...oldUserSelections, ethereumAccount: undefined, })); Alert.alert( 'Registration attempt timed out', 'Please try to connect your Ethereum wallet again', [{ text: 'OK', onPress: navigateToConnectEthereum }], { cancelable: false, }, ); }, [setCachedSelections, navigateToConnectEthereum]); const onProceed = React.useCallback(async () => { setRegistrationInProgress(true); try { await register({ ...userSelections, clearCachedSelections, onNonceExpired, }); } finally { setRegistrationInProgress(false); } }, [register, userSelections, clearCachedSelections, onNonceExpired]); React.useEffect(() => { if (!registrationInProgress) { return undefined; } navigation.setOptions({ gestureEnabled: false, headerLeft: null, }); const removeListener = navigation.addListener('beforeRemove', e => { if (e.data.action.type !== logInActionType) { e.preventDefault(); } }); return () => { navigation.setOptions({ gestureEnabled: true, headerLeft: undefined, }); removeListener(); }; }, [navigation, registrationInProgress]); const styles = useStyles(unboundStyles); const termsNotice = ( By registering, you are agreeing to our{' '} Terms of Use {' and '} Privacy Policy . ); return ( Finish registration {termsNotice} ); } const unboundStyles = { scrollViewContentContainer: { flexGrow: 1, }, header: { fontSize: 24, color: 'panelForegroundLabel', paddingBottom: 16, }, body: { fontFamily: 'Arial', fontSize: 15, lineHeight: 20, color: 'panelForegroundSecondaryLabel', paddingBottom: 16, }, commSwooshContainer: { flexGrow: 1, flexShrink: 1, alignItems: 'center', justifyContent: 'center', }, commSwoosh: { resizeMode: 'center', width: '100%', height: '100%', }, hyperlinkText: { color: 'purpleLink', }, }; export default RegistrationTerms; diff --git a/native/account/registration/siwe-backup-message-creation.react.js b/native/account/registration/siwe-backup-message-creation.react.js index 89558815e..2320b30b6 100644 --- a/native/account/registration/siwe-backup-message-creation.react.js +++ b/native/account/registration/siwe-backup-message-creation.react.js @@ -1,254 +1,255 @@ // @flow import Icon from '@expo/vector-icons/MaterialIcons.js'; import invariant from 'invariant'; import * as React from 'react'; -import { View, Text, Alert } from 'react-native'; +import { View, Text } from 'react-native'; import { type SIWEResult, SIWEMessageTypes } from 'lib/types/siwe-types.js'; import RegistrationButtonContainer from './registration-button-container.react.js'; import RegistrationButton from './registration-button.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, AccountSelection, AvatarData, } from './registration-types.js'; import { type NavigationRoute, RegistrationTermsRouteName, } from '../../navigation/route-names.js'; import { useStyles } from '../../themes/colors.js'; +import Alert from '../../utils/alert.js'; import SIWEPanel from '../siwe-panel.react.js'; type PanelState = 'closed' | 'opening' | 'open' | 'closing'; type CreateSIWEBackupMessageBaseProps = { +onSuccessfulWalletSignature: (result: SIWEResult) => void, +onExistingWalletSignature?: () => void, +onSkip?: () => void, }; const CreateSIWEBackupMessageBase: React.ComponentType = React.memo( function CreateSIWEBackupMessageBase( props: CreateSIWEBackupMessageBaseProps, ): React.Node { const { onSuccessfulWalletSignature, onExistingWalletSignature, onSkip } = props; const styles = useStyles(unboundStyles); const [panelState, setPanelState] = React.useState('closed'); const openPanel = React.useCallback(() => { setPanelState('opening'); }, []); const onPanelClosed = React.useCallback(() => { setPanelState('closed'); }, []); const onPanelClosing = React.useCallback(() => { setPanelState('closing'); }, []); const siwePanelSetLoading = React.useCallback( (loading: boolean) => { if (panelState === 'closing' || panelState === 'closed') { return; } setPanelState(loading ? 'opening' : 'open'); }, [panelState], ); let siwePanel; if (panelState !== 'closed') { siwePanel = ( ); } const newSignatureButtonText = onExistingWalletSignature ? 'Encrypt with new signature' : 'Encrypt with Ethereum signature'; const newSignatureButtonVariant = onExistingWalletSignature ? 'outline' : 'enabled'; let useExistingSignatureButton; if (onExistingWalletSignature) { useExistingSignatureButton = ( ); } let onSkipButton; if (onSkip) { onSkipButton = ( ); } return ( <> Encrypting your Comm backup To make sure we can’t see your data, Comm encrypts your backup using a signature from your wallet. This signature is private and never leaves your device, unlike the prior signature, which is public. This signature ensures that you can always recover your data as long as you still control your wallet. {useExistingSignatureButton} {onSkipButton} {siwePanel} ); }, ); export type CreateSIWEBackupMessageParams = { +userSelections: { +coolOrNerdMode?: ?CoolOrNerdMode, +keyserverURL?: ?string, +farcasterID: ?string, +accountSelection: AccountSelection, +avatarData: ?AvatarData, }, }; type Props = { +navigation: RegistrationNavigationProp<'CreateSIWEBackupMessage'>, +route: NavigationRoute<'CreateSIWEBackupMessage'>, }; function CreateSIWEBackupMessage(props: Props): React.Node { const { navigate } = props.navigation; const { params } = props.route; const { userSelections } = params; const registrationContext = React.useContext(RegistrationContext); invariant(registrationContext, 'registrationContext should be set'); const { cachedSelections, setCachedSelections } = registrationContext; const onSuccessfulWalletSignature = React.useCallback( (result: SIWEResult) => { const selectedEthereumAddress = userSelections.accountSelection.address; const { message, signature, address } = result; if (address !== selectedEthereumAddress) { Alert.alert( 'Mismatched Ethereum address', 'You picked a different wallet than the one you use to sign in.', ); return; } const newUserSelections = { ...userSelections, siweBackupSecrets: { message, signature }, }; setCachedSelections(oldUserSelections => ({ ...oldUserSelections, siweBackupSecrets: { message, signature }, })); navigate<'RegistrationTerms'>({ name: RegistrationTermsRouteName, params: { userSelections: newUserSelections }, }); }, [navigate, setCachedSelections, userSelections], ); const { siweBackupSecrets } = cachedSelections; const onExistingWalletSignature = React.useCallback(() => { const registrationTermsParams = { userSelections: { ...userSelections, siweBackupSecrets, }, }; navigate<'RegistrationTerms'>({ name: RegistrationTermsRouteName, params: registrationTermsParams, }); }, [navigate, siweBackupSecrets, userSelections]); if (siweBackupSecrets) { return ( ); } return ( ); } const unboundStyles = { scrollViewContentContainer: { flexGrow: 1, }, header: { fontSize: 24, color: 'panelForegroundLabel', paddingBottom: 16, }, body: { fontFamily: 'Arial', fontSize: 15, lineHeight: 20, color: 'panelForegroundSecondaryLabel', paddingBottom: 16, }, siweBackupIcon: { color: 'panelForegroundIcon', }, siweBackupIconContainer: { flexGrow: 1, alignItems: 'center', justifyContent: 'center', }, }; export { CreateSIWEBackupMessageBase, CreateSIWEBackupMessage }; diff --git a/native/profile/backup-menu.react.js b/native/profile/backup-menu.react.js index 6fc70ba73..131f92327 100644 --- a/native/profile/backup-menu.react.js +++ b/native/profile/backup-menu.react.js @@ -1,147 +1,148 @@ // @flow import * as React from 'react'; -import { Alert, Switch, Text, View } from 'react-native'; +import { Switch, Text, View } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; import { getMessageForException } from 'lib/utils/errors.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import type { ProfileNavigationProp } from './profile.react.js'; import { useClientBackup } from '../backup/use-client-backup.js'; import Button from '../components/button.react.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { setLocalSettingsActionType } from '../redux/action-types.js'; import { useSelector } from '../redux/redux-utils.js'; import { useColors, useStyles } from '../themes/colors.js'; +import Alert from '../utils/alert.js'; type Props = { +navigation: ProfileNavigationProp<'BackupMenu'>, +route: NavigationRoute<'BackupMenu'>, }; // eslint-disable-next-line no-unused-vars function BackupMenu(props: Props): React.Node { const styles = useStyles(unboundStyles); const dispatch = useDispatch(); const colors = useColors(); const isBackupEnabled = useSelector( state => state.localSettings.isBackupEnabled, ); const { uploadBackupProtocol, restoreBackupProtocol } = useClientBackup(); const uploadBackup = React.useCallback(async () => { let message = 'Success'; try { await uploadBackupProtocol(); } catch (e) { message = `Backup upload error: ${String(getMessageForException(e))}`; console.error(message); } Alert.alert('Upload protocol result', message); }, [uploadBackupProtocol]); const testRestore = React.useCallback(async () => { let message = 'success'; try { await restoreBackupProtocol(); } catch (e) { message = `Backup restore error: ${String(getMessageForException(e))}`; console.error(message); } Alert.alert('Restore protocol result', message); }, [restoreBackupProtocol]); const onBackupToggled = React.useCallback( (value: boolean) => { dispatch({ type: setLocalSettingsActionType, payload: { isBackupEnabled: value }, }); }, [dispatch], ); return ( SETTINGS Toggle automatic backup ACTIONS ); } const unboundStyles = { scrollViewContentContainer: { paddingTop: 24, }, scrollView: { backgroundColor: 'panelBackground', }, section: { backgroundColor: 'panelForeground', borderBottomWidth: 1, borderColor: 'panelForegroundBorder', borderTopWidth: 1, marginBottom: 24, marginVertical: 2, }, header: { color: 'panelBackgroundLabel', fontSize: 12, fontWeight: '400', paddingBottom: 3, paddingHorizontal: 24, }, submenuButton: { flexDirection: 'row', paddingHorizontal: 24, paddingVertical: 10, alignItems: 'center', }, submenuText: { color: 'panelForegroundLabel', flex: 1, fontSize: 16, }, row: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 24, paddingVertical: 14, }, }; export default BackupMenu; diff --git a/native/profile/farcaster-account-settings.react.js b/native/profile/farcaster-account-settings.react.js index 427bf2664..03cce29bc 100644 --- a/native/profile/farcaster-account-settings.react.js +++ b/native/profile/farcaster-account-settings.react.js @@ -1,144 +1,145 @@ // @flow import * as React from 'react'; -import { View, Alert } from 'react-native'; +import { View } from 'react-native'; 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 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 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; diff --git a/native/redux/redux-debug-utils.js b/native/redux/redux-debug-utils.js index 72b9060d7..59d515ea4 100644 --- a/native/redux/redux-debug-utils.js +++ b/native/redux/redux-debug-utils.js @@ -1,9 +1,9 @@ // @flow -import { Alert } from 'react-native'; +import Alert from '../utils/alert.js'; function onStateDifference(message: string) { Alert.alert('State difference found', message); } export { onStateDifference }; diff --git a/native/utils/farcaster-utils.js b/native/utils/farcaster-utils.js index c68de939f..1363d7e4c 100644 --- a/native/utils/farcaster-utils.js +++ b/native/utils/farcaster-utils.js @@ -1,41 +1,41 @@ // @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'; +import Alert from '../utils/alert.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 };