diff --git a/native/navigation/invite-link-modal.react.js b/native/navigation/invite-link-modal.react.js index 49a74c35b..716e80054 100644 --- a/native/navigation/invite-link-modal.react.js +++ b/native/navigation/invite-link-modal.react.js @@ -1,244 +1,254 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { View, Text, ActivityIndicator } from 'react-native'; import { joinThread, joinThreadActionTypes, } from 'lib/actions/thread-actions.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; import type { InviteLinkVerificationResponse } from 'lib/types/link-types.js'; import { useDispatchActionPromise, useServerCall, } from 'lib/utils/action-utils.js'; import { nonThreadCalendarQuery } from './nav-selectors.js'; import { NavContext } from './navigation-context.js'; import type { RootNavigationProp } from './root-navigator.react.js'; import type { NavigationRoute } from './route-names.js'; import Button from '../components/button.react.js'; import Modal from '../components/modal.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; export type InviteLinkModalParams = { +invitationDetails: InviteLinkVerificationResponse, +secret: string, }; type Props = { +navigation: RootNavigationProp<'InviteLinkModal'>, +route: NavigationRoute<'InviteLinkModal'>, }; function InviteLinkModal(props: Props): React.Node { const styles = useStyles(unboundStyles); const { invitationDetails, secret } = props.route.params; React.useEffect(() => { if (invitationDetails.status === 'already_joined') { props.navigation.goBack(); } }, [invitationDetails.status, props.navigation]); const header = React.useMemo(() => { if (invitationDetails.status === 'valid') { return ( <> You have been invited to join {invitationDetails.community.name} ); } return ( <> Invite invalid This invite link may be expired, please try again with another invite link ); }, [ invitationDetails, styles.communityName, styles.invalidInviteExplanation, styles.invalidInviteTitle, styles.invitation, ]); const callJoinThread = useServerCall(joinThread); const navContext = React.useContext(NavContext); const calendarQuery = useSelector(state => nonThreadCalendarQuery({ redux: state, navContext, }), ); const communityID = invitationDetails.community?.id; const createJoinCommunityAction = React.useCallback(async () => { invariant( communityID, 'CommunityID should be present while calling this function', ); const query = calendarQuery(); - const result = await callJoinThread({ - threadID: communityID, - calendarQuery: { - startDate: query.startDate, - endDate: query.endDate, - filters: [ - ...query.filters, - { type: 'threads', threadIDs: [communityID] }, - ], - }, - inviteLinkSecret: secret, - }); - props.navigation.goBack(); - return result; + try { + const result = await callJoinThread({ + threadID: communityID, + calendarQuery: { + startDate: query.startDate, + endDate: query.endDate, + filters: [ + ...query.filters, + { type: 'threads', threadIDs: [communityID] }, + ], + }, + inviteLinkSecret: secret, + }); + props.navigation.goBack(); + return result; + } catch (e) { + props.navigation.setParams({ + invitationDetails: { + status: 'invalid', + }, + secret, + }); + throw e; + } }, [calendarQuery, callJoinThread, communityID, props.navigation, secret]); const dispatchActionPromise = useDispatchActionPromise(); const joinCommunity = React.useCallback(() => { dispatchActionPromise(joinThreadActionTypes, createJoinCommunityAction()); }, [createJoinCommunityAction, dispatchActionPromise]); const joinThreadLoadingStatus = useSelector(joinThreadLoadingStatusSelector); const buttons = React.useMemo(() => { if (invitationDetails.status === 'valid') { const joinButtonContent = joinThreadLoadingStatus === 'loading' ? ( ) : ( Accept Invite ); return ( <> ); } return ( ); }, [ invitationDetails.status, joinCommunity, joinThreadLoadingStatus, props.navigation.goBack, styles.activityIndicatorStyle, styles.button, styles.buttonPrimary, styles.buttonSecondary, styles.buttonText, styles.gap, ]); return ( {header} {buttons} ); } const joinThreadLoadingStatusSelector = createLoadingStatusSelector( joinThreadActionTypes, ); const unboundStyles = { modal: { backgroundColor: 'modalForeground', paddingVertical: 24, paddingHorizontal: 16, flex: 0, }, invitation: { color: 'whiteText', textAlign: 'center', fontSize: 14, fontWeight: '400', lineHeight: 22, marginBottom: 24, }, communityName: { color: 'whiteText', textAlign: 'center', fontSize: 18, fontWeight: '500', lineHeight: 24, }, invalidInviteTitle: { color: 'whiteText', textAlign: 'center', fontSize: 22, fontWeight: '500', lineHeight: 28, marginBottom: 24, }, invalidInviteExplanation: { color: 'whiteText', textAlign: 'center', fontSize: 14, fontWeight: '400', lineHeight: 22, }, separator: { height: 1, backgroundColor: 'modalSeparator', marginVertical: 24, }, gap: { marginBottom: 16, }, button: { borderRadius: 8, paddingVertical: 12, paddingHorizontal: 24, }, buttonPrimary: { backgroundColor: 'purpleButton', }, buttonSecondary: { borderColor: 'secondaryButtonBorder', borderWidth: 1, }, buttonText: { color: 'whiteText', textAlign: 'center', fontSize: 16, fontWeight: '500', lineHeight: 24, }, activityIndicatorStyle: { paddingVertical: 2, }, }; export default InviteLinkModal;