diff --git a/lib/shared/community-utils.js b/lib/shared/community-utils.js index 85edffbd9..1cbc95d3f 100644 --- a/lib/shared/community-utils.js +++ b/lib/shared/community-utils.js @@ -1,124 +1,134 @@ // @flow import * as React from 'react'; import { createOrUpdateFarcasterChannelTagActionTypes, useCreateOrUpdateFarcasterChannelTag, deleteFarcasterChannelTagActionTypes, useDeleteFarcasterChannelTag, } from '../actions/community-actions.js'; import { createLoadingStatusSelector } from '../selectors/loading-selectors.js'; import type { SetState } from '../types/hook-types.js'; import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; import { useSelector } from '../utils/redux-utils.js'; function farcasterChannelTagBlobHash(farcasterChannelID: string): string { return `farcaster_channel_tag_${farcasterChannelID}`; } const createOrUpdateFarcasterChannelTagStatusSelector = createLoadingStatusSelector(createOrUpdateFarcasterChannelTagActionTypes); function useCreateFarcasterChannelTag( commCommunityID: string, setError: SetState, + onSuccessCallback?: () => mixed, ): { +createTag: (farcasterChannelID: string) => mixed, +isLoading: boolean, } { const dispatchActionPromise = useDispatchActionPromise(); const createOrUpdateFarcasterChannelTag = useCreateOrUpdateFarcasterChannelTag(); const createCreateOrUpdateActionPromise = React.useCallback( async (farcasterChannelID: string) => { try { - return await createOrUpdateFarcasterChannelTag({ + const res = await createOrUpdateFarcasterChannelTag({ commCommunityID, farcasterChannelID, }); + + onSuccessCallback?.(); + + return res; } catch (e) { setError(e.message); throw e; } }, - [commCommunityID, createOrUpdateFarcasterChannelTag, setError], + [ + commCommunityID, + createOrUpdateFarcasterChannelTag, + onSuccessCallback, + setError, + ], ); const createTag = React.useCallback( (farcasterChannelID: string) => { void dispatchActionPromise( createOrUpdateFarcasterChannelTagActionTypes, createCreateOrUpdateActionPromise(farcasterChannelID), ); }, [createCreateOrUpdateActionPromise, dispatchActionPromise], ); const createOrUpdateFarcasterChannelTagStatus = useSelector( createOrUpdateFarcasterChannelTagStatusSelector, ); const isLoading = createOrUpdateFarcasterChannelTagStatus === 'loading'; return { createTag, isLoading, }; } const deleteFarcasterChannelTagStatusSelector = createLoadingStatusSelector( deleteFarcasterChannelTagActionTypes, ); function useRemoveFarcasterChannelTag( commCommunityID: string, farcasterChannelID: string, setError: SetState, ): { +removeTag: () => mixed, +isLoading: boolean, } { const dispatchActionPromise = useDispatchActionPromise(); const deleteFarcasterChannelTag = useDeleteFarcasterChannelTag(); const createDeleteActionPromise = React.useCallback(async () => { try { return await deleteFarcasterChannelTag({ commCommunityID, farcasterChannelID, }); } catch (e) { setError(e.message); throw e; } }, [ commCommunityID, deleteFarcasterChannelTag, farcasterChannelID, setError, ]); const removeTag = React.useCallback(() => { void dispatchActionPromise( deleteFarcasterChannelTagActionTypes, createDeleteActionPromise(), ); }, [createDeleteActionPromise, dispatchActionPromise]); const deleteFarcasterChannelTagStatus = useSelector( deleteFarcasterChannelTagStatusSelector, ); const isLoading = deleteFarcasterChannelTagStatus === 'loading'; return { removeTag, isLoading, }; } export { farcasterChannelTagBlobHash, useCreateFarcasterChannelTag, useRemoveFarcasterChannelTag, }; diff --git a/native/community-settings/tag-farcaster-channel/tag-farcaster-channel-by-name.react.js b/native/community-settings/tag-farcaster-channel/tag-farcaster-channel-by-name.react.js index e1a9942ae..c0be6b80a 100644 --- a/native/community-settings/tag-farcaster-channel/tag-farcaster-channel-by-name.react.js +++ b/native/community-settings/tag-farcaster-channel/tag-farcaster-channel-by-name.react.js @@ -1,150 +1,152 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { View, Text } from 'react-native'; import { NeynarClientContext } from 'lib/components/neynar-client-provider.react.js'; import { useCreateFarcasterChannelTag } from 'lib/shared/community-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 TextInput from '../../components/text-input.react.js'; import type { NavigationRoute } from '../../navigation/route-names.js'; import { useStyles, useColors } from '../../themes/colors.js'; export type TagFarcasterChannelByNameParams = { +communityID: string, }; type Props = { +navigation: TagFarcasterChannelNavigationProp<'TagFarcasterChannelByName'>, +route: NavigationRoute<'TagFarcasterChannelByName'>, }; function TagFarcasterChannelByName(prop: Props): React.Node { const { navigation, route } = prop; const { goBack } = navigation; const { communityID } = route.params; const styles = useStyles(unboundStyles); const colors = useColors(); const [channelSelectionText, setChannelSelectionText] = React.useState(''); const [error, setError] = React.useState(null); const neynarClientContext = React.useContext(NeynarClientContext); invariant(neynarClientContext, 'NeynarClientContext is missing'); const { createTag, isLoading } = useCreateFarcasterChannelTag( communityID, setError, + goBack, ); const onPressTagChannel = React.useCallback(async () => { const channelInfo = await neynarClientContext.client.fetchFarcasterChannelByName( channelSelectionText, ); if (!channelInfo) { setError('channel_not_found'); return; } createTag(channelInfo.id); - - goBack(); - }, [channelSelectionText, createTag, goBack, neynarClientContext.client]); + }, [channelSelectionText, createTag, neynarClientContext.client]); const errorMessage = React.useMemo(() => { if (!error) { - return null; + return ; } return ( {tagFarcasterChannelErrorMessages[error] ?? 'Unknown error.'} ); - }, [error, styles.error]); + }, [error, styles.error, styles.errorPlaceholder]); let submitButtonVariant = 'disabled'; if (isLoading) { submitButtonVariant = 'loading'; } else if (channelSelectionText.length > 0) { submitButtonVariant = 'enabled'; } return ( CHANNEL NAME {errorMessage} ); } const unboundStyles = { container: { paddingTop: 24, }, header: { color: 'panelBackgroundLabel', fontSize: 12, fontWeight: '400', paddingBottom: 4, paddingHorizontal: 16, }, panelSectionContainer: { backgroundColor: 'panelForeground', padding: 16, borderBottomColor: 'panelSeparator', borderBottomWidth: 1, borderTopColor: 'panelSeparator', borderTopWidth: 1, }, inputContainer: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12, borderWidth: 1, borderColor: 'panelSecondaryForegroundBorder', borderRadius: 8, marginBottom: 8, }, input: { color: 'panelForegroundLabel', fontSize: 16, }, error: { fontSize: 12, fontWeight: '400', lineHeight: 18, textAlign: 'center', color: 'redText', }, + errorPlaceholder: { + height: 18, + }, }; export default TagFarcasterChannelByName;