diff --git a/native/invite-links/invite-links-button.react.js b/native/invite-links/invite-links-button.react.js index 516fa2887..f7aa6b8d4 100644 --- a/native/invite-links/invite-links-button.react.js +++ b/native/invite-links/invite-links-button.react.js @@ -1,134 +1,134 @@ // @flow import { useActionSheet } from '@expo/react-native-action-sheet'; import { useNavigation } from '@react-navigation/native'; import * as React from 'react'; import { TouchableOpacity, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { primaryInviteLinksSelector } from 'lib/selectors/invite-links-selectors.js'; import { threadHasPermission } from 'lib/shared/thread-utils.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import { InviteLinkNavigatorRouteName, ManagePublicLinkRouteName, ViewInviteLinksRouteName, } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles } from '../themes/colors.js'; type Props = { +community: ThreadInfo, }; function InviteLinksButton(props: Props): React.Node { const { community } = props; const inviteLink = useSelector(primaryInviteLinksSelector)[community.id]; const { navigate } = useNavigation(); const navigateToInviteLinksView = React.useCallback(() => { if (!inviteLink || !community) { return; } navigate<'InviteLinkNavigator'>(InviteLinkNavigatorRouteName, { screen: ViewInviteLinksRouteName, params: { community, }, }); }, [community, inviteLink, navigate]); const navigateToManagePublicLinkView = React.useCallback(() => { navigate<'InviteLinkNavigator'>(InviteLinkNavigatorRouteName, { screen: ManagePublicLinkRouteName, params: { community, }, }); }, [community, navigate]); const insets = useSafeAreaInsets(); const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme); const styles = useStyles(unboundStyles); const { showActionSheetWithOptions } = useActionSheet(); const actions = React.useMemo(() => { if (!community) { return null; } const result = []; const canManageLinks = threadHasPermission( community, threadPermissions.MANAGE_INVITE_LINKS, ); if (canManageLinks) { result.push({ - label: 'Manage Invite Links', + label: 'Manage invite links', action: navigateToManagePublicLinkView, }); } if (inviteLink) { result.push({ - label: 'Invite Link', + label: 'Invite link', action: navigateToInviteLinksView, }); } if (result.length > 0) { return result; } return null; }, [ community, inviteLink, navigateToInviteLinksView, navigateToManagePublicLinkView, ]); const openActionSheet = React.useCallback(() => { if (!actions) { return; } const options = [...actions.map(a => a.label), 'Cancel']; showActionSheetWithOptions( { options, cancelButtonIndex: options.length - 1, containerStyle: { paddingBottom: insets.bottom, }, userInterfaceStyle: activeTheme ?? 'dark', }, selectedIndex => { if (selectedIndex !== undefined && selectedIndex < actions.length) { actions[selectedIndex].action(); } }, ); }, [actions, activeTheme, insets.bottom, showActionSheetWithOptions]); let button = null; if (actions) { button = ( ); } return {button}; } const unboundStyles = { button: { color: 'drawerItemLabelLevel0', }, container: { width: 22, }, }; export default InviteLinksButton; diff --git a/native/invite-links/invite-links-navigator.react.js b/native/invite-links/invite-links-navigator.react.js index c419626b2..052c884d4 100644 --- a/native/invite-links/invite-links-navigator.react.js +++ b/native/invite-links/invite-links-navigator.react.js @@ -1,97 +1,93 @@ // @flow import { createStackNavigator, type StackNavigationHelpers, type StackNavigationProp, } from '@react-navigation/stack'; import * as React from 'react'; import { SafeAreaView } from 'react-native-safe-area-context'; import ManagePublicLinkScreen from './manage-public-link-screen.react.js'; import ViewInviteLinksHeaderLeftButton from './view-invite-links-header-left-button.react.js'; -import ViewInviteLinksHeaderTitle from './view-invite-links-header-title.react.js'; import ViewInviteLinksScreen from './view-invite-links-screen.react.js'; import HeaderBackButton from '../navigation/header-back-button.react.js'; import { defaultStackScreenOptions } from '../navigation/options.js'; import type { RootNavigationProp } from '../navigation/root-navigator.react.js'; import { type InviteLinkParamList, ViewInviteLinksRouteName, type ScreenParamList, ManagePublicLinkRouteName, } from '../navigation/route-names.js'; import { useColors, useStyles } from '../themes/colors.js'; const safeAreaEdges = ['bottom']; export type InviteLinksNavigationProps< RouteName: $Keys = $Keys, > = StackNavigationProp; const InviteLinksStack = createStackNavigator< ScreenParamList, InviteLinkParamList, StackNavigationHelpers, >(); -const viewInviteLinksOptions = ({ route }) => ({ - // eslint-disable-next-line react/display-name - headerTitle: props => ( - - ), +const viewInviteLinksOptions = { + headerTitle: 'Invite link', headerLeft: ViewInviteLinksHeaderLeftButton, headerBackImage: () => null, headerBackTitleStyle: { marginLeft: 20 }, -}); +}; const managePublicLinkOptions = { - headerTitle: 'Public Link', + headerTitle: 'Public link', headerBackTitleVisible: false, headerLeft: HeaderBackButton, }; type Props = { +navigation: RootNavigationProp<'InviteLinkNavigator'>, ... }; // eslint-disable-next-line no-unused-vars function InviteLinksNavigator(props: Props): React.Node { const styles = useStyles(unboundStyles); const colors = useColors(); const screenOptions = React.useMemo( () => ({ ...defaultStackScreenOptions, headerStyle: { backgroundColor: colors.modalBackground, borderBottomWidth: 1, }, }), [colors.modalBackground], ); return ( ); } const unboundStyles = { container: { flex: 1, backgroundColor: 'modalBackground', }, }; export default InviteLinksNavigator; diff --git a/native/invite-links/manage-public-link-screen.react.js b/native/invite-links/manage-public-link-screen.react.js index 9e9f10573..627b1c8ae 100644 --- a/native/invite-links/manage-public-link-screen.react.js +++ b/native/invite-links/manage-public-link-screen.react.js @@ -1,206 +1,205 @@ // @flow import * as React from 'react'; import { Text, View, Alert } from 'react-native'; import { inviteLinkUrl } from 'lib/facts/links.js'; import { useInviteLinksActions } from 'lib/hooks/invite-links.js'; import { primaryInviteLinksSelector } from 'lib/selectors/invite-links-selectors.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import Button from '../components/button.react.js'; import TextInput from '../components/text-input.react.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'; export type ManagePublicLinkScreenParams = { +community: ThreadInfo, }; type Props = { +navigation: RootNavigationProp<'ManagePublicLink'>, +route: NavigationRoute<'ManagePublicLink'>, }; function ManagePublicLinkScreen(props: Props): React.Node { const { community } = props.route.params; const inviteLink = useSelector(primaryInviteLinksSelector)[community.id]; const { error, isLoading, name, setName, createOrUpdateInviteLink, disableInviteLink, } = useInviteLinksActions(community.id, inviteLink); const styles = useStyles(unboundStyles); let errorComponent = null; if (error) { errorComponent = {error}; } const onDisableButtonClick = React.useCallback(() => { Alert.alert( 'Disable public link', - 'Are you sure you want to disable your public link? Members who have your community’s public link but have not joined will not able to with the disabled link. \n' + + 'Are you sure you want to disable your public link?\n' + '\n' + - 'Other communities may also claim your previous public link url.', + 'Other communities will be able to claim the same URL.', [ { text: 'Confirm disable', style: 'destructive', onPress: disableInviteLink, }, { text: 'Cancel', }, ], { cancelable: true, }, ); }, [disableInviteLink]); let disablePublicLinkButton = null; if (inviteLink) { disablePublicLinkButton = ( ); } return ( - Let your community be more accessible with your own unique public - link. By enabling a public link, you are allowing anyone who has your - link to join your community.{'\n\n'} - Editing your community’s public link allows other communities to claim - your previous URL. + Invite links make it easy for your friends to join your community. + Anybody who knows your community’s invite link will be able to join + it.{'\n\n'}Note that if you change your public link’s URL, other + communities will be able to claim the old URL. INVITE URL {inviteLinkUrl('')} {errorComponent} {disablePublicLinkButton} ); } const unboundStyles = { sectionTitle: { fontSize: 14, fontWeight: '400', lineHeight: 20, color: 'modalBackgroundLabel', paddingHorizontal: 16, paddingBottom: 4, marginTop: 24, }, section: { borderBottomColor: 'modalSeparator', borderBottomWidth: 1, borderTopColor: 'modalSeparator', borderTopWidth: 1, backgroundColor: 'modalForeground', padding: 16, }, sectionText: { fontSize: 14, fontWeight: '400', lineHeight: 22, color: 'modalBackgroundLabel', }, inviteLink: { flexDirection: 'row', alignItems: 'center', marginBottom: 8, }, inviteLinkPrefix: { fontSize: 14, fontWeight: '400', lineHeight: 22, color: 'disabledButtonText', marginRight: 2, }, input: { color: 'panelForegroundLabel', borderColor: 'panelSecondaryForegroundBorder', borderWidth: 1, borderRadius: 8, paddingVertical: 13, paddingHorizontal: 16, flex: 1, }, button: { borderRadius: 8, paddingVertical: 12, paddingHorizontal: 24, marginTop: 8, }, buttonPrimary: { backgroundColor: 'purpleButton', }, destructiveButtonContainer: { margin: 16, }, destructiveButton: { borderWidth: 1, borderRadius: 8, borderColor: 'vibrantRedButton', }, destructiveButtonText: { fontSize: 16, fontWeight: '500', lineHeight: 24, color: 'vibrantRedButton', textAlign: 'center', }, buttonText: { color: 'whiteText', textAlign: 'center', fontWeight: '500', fontSize: 16, lineHeight: 24, }, error: { fontSize: 12, fontWeight: '400', lineHeight: 18, textAlign: 'center', color: 'redText', }, }; export default ManagePublicLinkScreen; diff --git a/native/invite-links/view-invite-links-header-title.react.js b/native/invite-links/view-invite-links-header-title.react.js deleted file mode 100644 index 112b292e8..000000000 --- a/native/invite-links/view-invite-links-header-title.react.js +++ /dev/null @@ -1,25 +0,0 @@ -// @flow - -import type { HeaderTitleInputProps } from '@react-navigation/elements'; -import { HeaderTitle } from '@react-navigation/elements'; -import * as React from 'react'; - -import type { ThreadInfo } from 'lib/types/thread-types.js'; -import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; -import { firstLine } from 'lib/utils/string-utils.js'; - -type Props = { - +community: ThreadInfo, - ...HeaderTitleInputProps, -}; -function ViewInviteLinksHeaderTitle(props: Props): React.Node { - const { community, ...rest } = props; - const { uiName } = useResolvedThreadInfo(community); - const title = `Invite people to ${firstLine(uiName)}`; - return {title}; -} - -const MemoizedViewInviteLinksHeaderTitle: React.ComponentType = - React.memo(ViewInviteLinksHeaderTitle); - -export default MemoizedViewInviteLinksHeaderTitle; diff --git a/native/invite-links/view-invite-links-screen.react.js b/native/invite-links/view-invite-links-screen.react.js index 6a272f3d8..9b75b1dd8 100644 --- a/native/invite-links/view-invite-links-screen.react.js +++ b/native/invite-links/view-invite-links-screen.react.js @@ -1,167 +1,167 @@ // @flow import Clipboard from '@react-native-clipboard/clipboard'; import * as React from 'react'; import { Text, View } from 'react-native'; import { TouchableOpacity } from 'react-native-gesture-handler'; import { inviteLinkUrl } from 'lib/facts/links.js'; import { primaryInviteLinksSelector } from 'lib/selectors/invite-links-selectors.js'; import { threadHasPermission } from 'lib/shared/thread-utils.js'; import type { InviteLink } from 'lib/types/link-types.js'; import { threadPermissions } from 'lib/types/thread-permission-types.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import SWMansionIcon from '../components/swmansion-icon.react.js'; import { displayActionResultModal } from '../navigation/action-result-modal.js'; import type { RootNavigationProp } from '../navigation/root-navigator.react.js'; import { ManagePublicLinkRouteName, type NavigationRoute, } from '../navigation/route-names.js'; import { useSelector } from '../redux/redux-utils.js'; import { useStyles, useColors } from '../themes/colors.js'; export type ViewInviteLinksScreenParams = { +community: ThreadInfo, }; type Props = { +navigation: RootNavigationProp<'ViewInviteLinks'>, +route: NavigationRoute<'ViewInviteLinks'>, }; const confirmCopy = () => displayActionResultModal('copied!'); function ViewInviteLinksScreen(props: Props): React.Node { const { community } = props.route.params; const inviteLink: ?InviteLink = useSelector(primaryInviteLinksSelector)[ community.id ]; const styles = useStyles(unboundStyles); const { modalForegroundLabel } = useColors(); const linkUrl = inviteLinkUrl(inviteLink?.name ?? ''); const onPressCopy = React.useCallback(() => { Clipboard.setString(linkUrl); setTimeout(confirmCopy); }, [linkUrl]); const { navigate } = props.navigation; const onEditButtonClick = React.useCallback(() => { navigate<'ManagePublicLink'>({ name: ManagePublicLinkRouteName, params: { community, }, }); }, [community, navigate]); const canManageLinks = threadHasPermission( community, threadPermissions.MANAGE_INVITE_LINKS, ); let publicLinkSection = null; if (inviteLink || canManageLinks) { let description; if (canManageLinks) { description = ( <> Public links allow unlimited uses and never expire. Edit public link ); } else { description = ( - Use this public link to invite your friends into the community! + Share this invite link to help your friends join your community! ); } publicLinkSection = ( <> PUBLIC LINK {linkUrl} Copy {description} ); } return {publicLinkSection}; } const unboundStyles = { container: { flex: 1, paddingTop: 24, }, sectionTitle: { fontSize: 12, fontWeight: '400', lineHeight: 18, color: 'modalBackgroundLabel', paddingHorizontal: 16, paddingBottom: 4, }, section: { borderBottomColor: 'modalSeparator', borderBottomWidth: 1, borderTopColor: 'modalSeparator', borderTopWidth: 1, backgroundColor: 'modalForeground', padding: 16, }, link: { paddingHorizontal: 16, paddingVertical: 9, marginBottom: 16, backgroundColor: 'inviteLinkButtonBackground', borderRadius: 20, flexDirection: 'row', justifyContent: 'space-between', }, linkText: { fontSize: 14, fontWeight: '400', lineHeight: 22, color: 'inviteLinkLinkColor', }, button: { flexDirection: 'row', alignItems: 'center', }, copy: { fontSize: 12, fontWeight: '400', lineHeight: 18, color: 'modalForegroundLabel', paddingLeft: 8, }, details: { fontSize: 12, fontWeight: '400', lineHeight: 18, color: 'modalForegroundLabel', }, editLinkButton: { color: 'purpleLink', }, }; export default ViewInviteLinksScreen; diff --git a/native/navigation/invite-link-modal.react.js b/native/navigation/invite-link-modal.react.js index 716e80054..b805a26f4 100644 --- a/native/navigation/invite-link-modal.react.js +++ b/native/navigation/invite-link-modal.react.js @@ -1,254 +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 + 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(); 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 + 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; diff --git a/web/invite-links/accept-invite-modal.react.js b/web/invite-links/accept-invite-modal.react.js index 148dc362f..3c807899d 100644 --- a/web/invite-links/accept-invite-modal.react.js +++ b/web/invite-links/accept-invite-modal.react.js @@ -1,133 +1,133 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { joinThread, joinThreadActionTypes, } from 'lib/actions/thread-actions.js'; import ModalOverlay from 'lib/components/modal-overlay.react.js'; import { useModalContext } from 'lib/components/modal-provider.react.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 css from './accept-invite-modal.css'; import Button, { buttonThemes } from '../components/button.react.js'; import { useSelector } from '../redux/redux-utils.js'; import { nonThreadCalendarQuery } from '../selectors/nav-selectors.js'; type Props = { +verificationResponse: InviteLinkVerificationResponse, +inviteSecret: string, }; function AcceptInviteModal(props: Props): React.Node { const { verificationResponse, inviteSecret } = props; const [isLinkValid, setIsLinkValid] = React.useState( verificationResponse.status === 'valid', ); const { popModal } = useModalContext(); React.useEffect(() => { if (verificationResponse.status === 'already_joined') { popModal(); } }, [popModal, verificationResponse.status]); const callJoinThread = useServerCall(joinThread); const calendarQuery = useSelector(nonThreadCalendarQuery); const communityID = verificationResponse.community?.id; const createJoinCommunityAction = React.useCallback(async () => { invariant( communityID, 'CommunityID should be present while calling this function', ); const query = calendarQuery(); try { const result = await callJoinThread({ threadID: communityID, calendarQuery: { startDate: query.startDate, endDate: query.endDate, filters: [ ...query.filters, { type: 'threads', threadIDs: [communityID] }, ], }, inviteLinkSecret: inviteSecret, }); popModal(); return result; } catch (e) { setIsLinkValid(false); throw e; } }, [calendarQuery, callJoinThread, communityID, inviteSecret, popModal]); const dispatchActionPromise = useDispatchActionPromise(); const joinCommunity = React.useCallback(() => { dispatchActionPromise(joinThreadActionTypes, createJoinCommunityAction()); }, [createJoinCommunityAction, dispatchActionPromise]); const joinThreadLoadingStatus = useSelector(joinThreadLoadingStatusSelector); let content; if (verificationResponse.status === 'valid' && isLinkValid) { const { community } = verificationResponse; content = ( <>
You have been invited to join
{community.name}

); } else { content = ( <>
Invite invalid
- This invite link may be expired, please try again with another - invite link + This invite link may be expired. Please try again with another + invite link.

); } return (
{content}
); } const joinThreadLoadingStatusSelector = createLoadingStatusSelector( joinThreadActionTypes, ); export default AcceptInviteModal; diff --git a/web/invite-links/view-invite-link-modal.react.js b/web/invite-links/view-invite-link-modal.react.js index de6b93540..4f59c1fb9 100644 --- a/web/invite-links/view-invite-link-modal.react.js +++ b/web/invite-links/view-invite-link-modal.react.js @@ -1,44 +1,33 @@ // @flow import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; -import { threadInfoSelector } from 'lib/selectors/thread-selectors.js'; import type { InviteLink } from 'lib/types/link-types.js'; -import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; import CopyInviteLinkButton from './copy-invite-link-button.react.js'; import css from './view-invite-link-modal.css'; import Modal from '../modals/modal.react.js'; -import { useSelector } from '../redux/redux-utils.js'; type Props = { +inviteLink: InviteLink, }; function ViewInviteLinkModal(props: Props): React.Node { const { inviteLink } = props; - const threadInfo = useSelector( - state => threadInfoSelector(state)[inviteLink.communityID], - ); - const resolvedThreadInfo = useResolvedThreadInfo(threadInfo); const { popModal } = useModalContext(); return ( - +
- Use this public link to invite your friends into the community! + Share this invite link to help your friends join your community!
Public link
); } export default ViewInviteLinkModal;