diff --git a/native/community-settings/tag-farcaster-channel/tag-farcaster-channel.react.js b/native/community-settings/tag-farcaster-channel/tag-channel-button.react.js copy from native/community-settings/tag-farcaster-channel/tag-farcaster-channel.react.js copy to native/community-settings/tag-farcaster-channel/tag-channel-button.react.js --- a/native/community-settings/tag-farcaster-channel/tag-farcaster-channel.react.js +++ b/native/community-settings/tag-farcaster-channel/tag-channel-button.react.js @@ -1,9 +1,10 @@ // @flow import { useActionSheet } from '@expo/react-native-action-sheet'; +import { useNavigation } from '@react-navigation/native'; import invariant from 'invariant'; import * as React from 'react'; -import { View, Text, Platform } from 'react-native'; +import { Text, View, Platform, ActivityIndicator } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { @@ -11,51 +12,45 @@ useCreateOrUpdateFarcasterChannelTag, } from 'lib/actions/community-actions.js'; import { NeynarClientContext } from 'lib/components/neynar-client-provider.react.js'; +import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { NeynarChannel } from 'lib/types/farcaster-types.js'; +import type { SetState } from 'lib/types/hook-types.js'; import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; -import type { TagFarcasterChannelNavigationProp } from './tag-farcaster-channel-navigator.react.js'; -import { tagFarcasterChannelErrorMessages } from './tag-farcaster-channel-utils.js'; -import RegistrationButton from '../../account/registration/registration-button.react.js'; -import { - TagFarcasterChannelByNameRouteName, - type NavigationRoute, -} from '../../navigation/route-names.js'; +import Button from '../../components/button.react.js'; +import { TagFarcasterChannelByNameRouteName } from '../../navigation/route-names.js'; import { useSelector } from '../../redux/redux-utils.js'; -import { useStyles } from '../../themes/colors.js'; +import { useStyles, useColors } from '../../themes/colors.js'; -export type TagFarcasterChannelParams = { - +communityID: string, -}; +const createOrUpdateFarcasterChannelTagStatusSelector = + createLoadingStatusSelector(createOrUpdateFarcasterChannelTagActionTypes); type Props = { - +navigation: TagFarcasterChannelNavigationProp<'TagFarcasterChannel'>, - +route: NavigationRoute<'TagFarcasterChannel'>, + +communityID: string, + +setError: SetState, }; -function TagFarcasterChannel(props: Props): React.Node { - const { navigation, route } = props; +function TagChannelButton(props: Props): React.Node { + const { communityID, setError } = props; - const { navigate } = navigation; - const { communityID } = route.params; + const { navigate } = useNavigation(); + const colors = useColors(); const styles = useStyles(unboundStyles); const fid = useCurrentUserFID(); invariant(fid, 'FID should be set'); - const [channelOptions, setChannelOptions] = React.useState< - $ReadOnlyArray, - >([]); - - const [error, setError] = React.useState(null); - const neynarClientContext = React.useContext(NeynarClientContext); invariant(neynarClientContext, 'NeynarClientContext is missing'); const { client } = neynarClientContext; + const [channelOptions, setChannelOptions] = React.useState< + $ReadOnlyArray, + >([]); + React.useEffect(() => { void (async () => { const channels = await client.fetchFollowedFarcasterChannels(fid); @@ -87,7 +82,7 @@ throw e; } }, - [communityID, createOrUpdateFarcasterChannelTag], + [communityID, createOrUpdateFarcasterChannelTag, setError], ); const onOptionSelected = React.useCallback( @@ -125,6 +120,7 @@ createCreateOrUpdateActionPromise, dispatchActionPromise, navigate, + setError, ], ); @@ -159,82 +155,54 @@ showActionSheetWithOptions, ]); - const errorMessage = React.useMemo(() => { - if (!error) { - return null; + const createOrUpdateFarcasterChannelTagStatus = useSelector( + createOrUpdateFarcasterChannelTagStatusSelector, + ); + const isLoadingCreateOrUpdateFarcasterChannelTag = + createOrUpdateFarcasterChannelTagStatus === 'loading'; + + const buttonContent = React.useMemo(() => { + if (isLoadingCreateOrUpdateFarcasterChannelTag) { + return ( + + ); } - return ( - - {tagFarcasterChannelErrorMessages[error] ?? 'Unknown error.'} - - ); - }, [error, styles.error]); - - const tagFarcasterChannel = React.useMemo( - () => ( - - - - Tag a Farcaster channel so followers can find your Comm community! - - - FARCASTER CHANNEL - - - - {errorMessage} - - ), - [ - styles.panelSectionContainer, - styles.sectionText, - styles.sectionHeaderText, - styles.errorContainer, - onPressTag, - errorMessage, - ], - ); + return Tag channel; + }, [ + colors.panelForegroundLabel, + isLoadingCreateOrUpdateFarcasterChannelTag, + styles.buttonText, + ]); - return tagFarcasterChannel; + return ( + + ); } const unboundStyles = { - panelSectionContainer: { - backgroundColor: 'panelForeground', - padding: 16, - borderBottomColor: 'panelSeparator', - borderBottomWidth: 1, - borderTopColor: 'panelSeparator', - borderTopWidth: 1, - }, - sectionText: { - color: 'panelForegroundLabel', - fontSize: 14, + button: { + borderRadius: 8, + paddingVertical: 12, + paddingHorizontal: 24, + backgroundColor: 'purpleButton', }, - sectionHeaderText: { - fontSize: 14, - fontWeight: '400', - lineHeight: 20, - color: 'panelForegroundLabel', - paddingHorizontal: 16, - paddingBottom: 4, - marginTop: 24, - }, - errorContainer: { - height: 18, - }, - error: { - fontSize: 12, - fontWeight: '400', - lineHeight: 18, + buttonText: { + color: 'whiteText', textAlign: 'center', - color: 'redText', + fontWeight: '500', + fontSize: 16, + lineHeight: 24, + }, + buttonContainer: { + height: 24, }, }; -export default TagFarcasterChannel; +export default TagChannelButton; diff --git a/native/community-settings/tag-farcaster-channel/tag-farcaster-channel.react.js b/native/community-settings/tag-farcaster-channel/tag-farcaster-channel.react.js --- a/native/community-settings/tag-farcaster-channel/tag-farcaster-channel.react.js +++ b/native/community-settings/tag-farcaster-channel/tag-farcaster-channel.react.js @@ -1,28 +1,12 @@ // @flow -import { useActionSheet } from '@expo/react-native-action-sheet'; -import invariant from 'invariant'; import * as React from 'react'; -import { View, Text, Platform } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; - -import { - createOrUpdateFarcasterChannelTagActionTypes, - useCreateOrUpdateFarcasterChannelTag, -} from 'lib/actions/community-actions.js'; -import { NeynarClientContext } from 'lib/components/neynar-client-provider.react.js'; -import type { NeynarChannel } from 'lib/types/farcaster-types.js'; -import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js'; -import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; +import { View, Text } from 'react-native'; +import TagChannelButton from './tag-channel-button.react.js'; import type { TagFarcasterChannelNavigationProp } from './tag-farcaster-channel-navigator.react.js'; import { tagFarcasterChannelErrorMessages } from './tag-farcaster-channel-utils.js'; -import RegistrationButton from '../../account/registration/registration-button.react.js'; -import { - TagFarcasterChannelByNameRouteName, - type NavigationRoute, -} from '../../navigation/route-names.js'; -import { useSelector } from '../../redux/redux-utils.js'; +import { type NavigationRoute } from '../../navigation/route-names.js'; import { useStyles } from '../../themes/colors.js'; export type TagFarcasterChannelParams = { @@ -35,130 +19,14 @@ }; function TagFarcasterChannel(props: Props): React.Node { - const { navigation, route } = props; + const { route } = props; - const { navigate } = navigation; const { communityID } = route.params; const styles = useStyles(unboundStyles); - const fid = useCurrentUserFID(); - invariant(fid, 'FID should be set'); - - const [channelOptions, setChannelOptions] = React.useState< - $ReadOnlyArray, - >([]); - const [error, setError] = React.useState(null); - const neynarClientContext = React.useContext(NeynarClientContext); - invariant(neynarClientContext, 'NeynarClientContext is missing'); - - const { client } = neynarClientContext; - - React.useEffect(() => { - void (async () => { - const channels = await client.fetchFollowedFarcasterChannels(fid); - - setChannelOptions(channels); - })(); - }, [client, fid]); - - const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme); - - const { showActionSheetWithOptions } = useActionSheet(); - - const insets = useSafeAreaInsets(); - - const dispatchActionPromise = useDispatchActionPromise(); - - const createOrUpdateFarcasterChannelTag = - useCreateOrUpdateFarcasterChannelTag(); - - const createCreateOrUpdateActionPromise = React.useCallback( - async (channelID: string) => { - try { - return await createOrUpdateFarcasterChannelTag({ - commCommunityID: communityID, - farcasterChannelID: channelID, - }); - } catch (e) { - setError(e.message); - throw e; - } - }, - [communityID, createOrUpdateFarcasterChannelTag], - ); - - const onOptionSelected = React.useCallback( - (selectedIndex: ?number) => { - if ( - selectedIndex === undefined || - selectedIndex === null || - selectedIndex > channelOptions.length - ) { - return; - } - - setError(null); - - // This is the "Other" option - if (selectedIndex === channelOptions.length) { - navigate<'TagFarcasterChannelByName'>({ - name: TagFarcasterChannelByNameRouteName, - params: { communityID }, - }); - - return; - } - - const channel = channelOptions[selectedIndex]; - - void dispatchActionPromise( - createOrUpdateFarcasterChannelTagActionTypes, - createCreateOrUpdateActionPromise(channel.id), - ); - }, - [ - channelOptions, - communityID, - createCreateOrUpdateActionPromise, - dispatchActionPromise, - navigate, - ], - ); - - const onPressTag = React.useCallback(() => { - const channelNames = channelOptions.map(channel => channel.name); - - const options = - Platform.OS === 'ios' - ? [...channelNames, 'Other', 'Cancel'] - : channelNames; - - const cancelButtonIndex = Platform.OS === 'ios' ? options.length - 1 : -1; - - const containerStyle = { - paddingBottom: insets.bottom, - }; - - showActionSheetWithOptions( - { - options, - cancelButtonIndex, - containerStyle, - userInterfaceStyle: activeTheme ?? 'dark', - }, - onOptionSelected, - ); - }, [ - activeTheme, - channelOptions, - insets.bottom, - onOptionSelected, - showActionSheetWithOptions, - ]); - const errorMessage = React.useMemo(() => { if (!error) { return null; @@ -181,11 +49,7 @@ FARCASTER CHANNEL - + {errorMessage} @@ -195,7 +59,7 @@ styles.sectionText, styles.sectionHeaderText, styles.errorContainer, - onPressTag, + communityID, errorMessage, ], );