MM-42835_Invite People - add email+user invites

This commit is contained in:
Julian Mondragon 2023-01-16 13:28:03 -05:00
parent 5816406040
commit 0b86c5129c
2 changed files with 101 additions and 72 deletions

View file

@ -3,7 +3,7 @@
import React, {useCallback, useEffect, useState, useRef} from 'react';
import {IntlShape, useIntl} from 'react-intl';
import {Keyboard, View, LayoutChangeEvent, Platform} from 'react-native';
import {Keyboard, View, LayoutChangeEvent} from 'react-native';
import {OptionsTopBarButton} from 'react-native-navigation';
import {SafeAreaView} from 'react-native-safe-area-context';
@ -28,33 +28,15 @@ import Summary from './summary';
import type {NavButtons} from '@typings/screens/navigation';
const CLOSE_BUTTON_ID = 'close-invite';
const BACK_BUTTON_ID = 'back-invite';
const SEND_BUTTON_ID = 'send-invite';
const SEARCH_TIMEOUT_MILLISECONDS = 200;
const TIMEOUT_MILLISECONDS = 200;
const DEFAULT_RESULT = {sent: [], notSent: []};
const makeLeftButton = (theme: Theme, type: LeftButtonType): OptionsTopBarButton => (
(type === LeftButtonType.BACK) ? (
{
id: BACK_BUTTON_ID,
icon: CompassIcon.getImageSourceSync(
Platform.select({
ios: 'arrow-back-ios',
default: 'arrow-left',
}),
24,
theme.sidebarHeaderTextColor,
),
testID: 'invite.back.button',
}
) : (
{
id: CLOSE_BUTTON_ID,
icon: CompassIcon.getImageSourceSync('close', 24, theme.sidebarHeaderTextColor),
testID: 'invite.close.button',
}
)
);
const makeLeftButton = (theme: Theme): OptionsTopBarButton => ({
id: CLOSE_BUTTON_ID,
icon: CompassIcon.getImageSourceSync('close', 24, theme.sidebarHeaderTextColor),
testID: 'invite.close.button',
});
const makeRightButton = (theme: Theme, formatMessage: IntlShape['formatMessage'], enabled: boolean): OptionsTopBarButton => ({
id: SEND_BUTTON_ID,
@ -105,11 +87,6 @@ enum Stage {
LOADING = 'loading',
}
enum LeftButtonType {
CLOSE = 'close',
BACK = 'back',
}
type InviteProps = {
componentId: string;
teamId: string;
@ -171,13 +148,12 @@ export default function Invite({
}, [serverUrl, teamId]);
const handleReset = () => {
setStage(Stage.LOADING);
setSendError('');
setTerm('');
setSearchResults([]);
setSelectedIds({});
setLoading(false);
setResult(DEFAULT_RESULT);
setStage(Stage.SELECTION);
setSendError('');
};
const handleClearSearch = useCallback(() => {
@ -196,7 +172,7 @@ export default function Invite({
searchTimeoutId.current = setTimeout(async () => {
await searchUsers(text);
setLoading(false);
}, SEARCH_TIMEOUT_MILLISECONDS);
}, TIMEOUT_MILLISECONDS);
}, [searchUsers]);
const handleSelectItem = useCallback((item: SearchResult) => {
@ -213,6 +189,15 @@ export default function Invite({
handleClearSearch();
}, [selectedIds, handleClearSearch]);
const handleRetry = () => {
setSendError('');
setStage(Stage.LOADING);
setTimeout(() => {
handleSend();
}, TIMEOUT_MILLISECONDS);
};
const handleSendError = () => {
setSendError(formatMessage({id: 'invite.send_error', defaultMessage: 'Something went wrong while trying to send invitations. Please check your network connection and try again.'}));
setResult(DEFAULT_RESULT);
@ -327,12 +312,11 @@ export default function Invite({
};
useNavButtonPressed(CLOSE_BUTTON_ID, componentId, closeModal, [closeModal]);
useNavButtonPressed(BACK_BUTTON_ID, componentId, handleReset, [handleReset]);
useNavButtonPressed(SEND_BUTTON_ID, componentId, handleSend, [handleSend]);
useEffect(() => {
const buttons: NavButtons = {
leftButtons: [makeLeftButton(theme, stage === Stage.RESULT && sendError ? LeftButtonType.BACK : LeftButtonType.CLOSE)],
leftButtons: [makeLeftButton(theme)],
rightButtons: stage === Stage.SELECTION ? [makeRightButton(theme, formatMessage, selectedCount > 0)] : [],
};
@ -379,7 +363,8 @@ export default function Invite({
selectedIds={selectedIds}
error={sendError}
onClose={closeModal}
onRetry={handleReset}
onRetry={handleRetry}
onBack={handleReset}
testID='invite.screen.summary'
/>
);

View file

@ -12,6 +12,7 @@ import AlertSvg from '@components/illustrations/alert';
import ErrorSvg from '@components/illustrations/error';
import SuccessSvg from '@components/illustrations/success';
import {useTheme} from '@context/theme';
import {t} from '@i18n';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
import {typography} from '@utils/typography';
@ -67,6 +68,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
},
summaryButtonContainer: {
flexGrow: 1,
flexDirection: 'row',
justifyContent: 'center',
maxWidth: MAX_WIDTH_CONTENT,
},
summaryButtonTextContainer: {
@ -83,6 +86,12 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
};
});
enum SummaryButtonType {
DONE = 'done',
RETRY = 'retry',
BACK = 'back',
}
type SummaryProps = {
result: Result;
selectedIds: {[id: string]: SearchResult};
@ -90,6 +99,7 @@ type SummaryProps = {
testID: string;
onClose: () => void;
onRetry: () => void;
onBack: () => void;
}
export default function Summary({
@ -99,8 +109,9 @@ export default function Summary({
testID,
onClose,
onRetry,
onBack,
}: SummaryProps) {
const {formatMessage} = useIntl();
const {formatMessage, locale} = useIntl();
const theme = useTheme();
const styles = getStyleSheet(theme);
@ -108,8 +119,6 @@ export default function Summary({
const sentCount = sent.length;
const notSentCount = notSent.length;
const styleButtonText = buttonTextStyle(theme, 'lg', 'primary');
const styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary');
const styleSummaryMessageText = useMemo(() => {
const style = [];
@ -163,14 +172,67 @@ export default function Summary({
);
}
const handleOnPressButton = useCallback(() => {
if (error) {
onRetry();
return;
const renderButton = useCallback((type: SummaryButtonType) => {
let onPress;
let iconName = '';
let text;
let styleButtonText = buttonTextStyle(theme, 'lg', 'primary');
let styleButtonBackground = buttonBackgroundStyle(theme, 'lg', 'primary');
const styleButtonIcon = [];
styleButtonIcon.push(styles.summaryButtonIcon);
switch (type) {
case SummaryButtonType.BACK:
onPress = onBack;
iconName = 'chevron-left';
text = {
id: t('invite.summary.back'),
defaultMessage: 'Go back',
};
styleButtonText = buttonTextStyle(theme, 'lg', 'tertiary');
styleButtonBackground = [buttonBackgroundStyle(theme, 'lg', 'tertiary'), {marginRight: 8}];
styleButtonIcon.push({color: theme.buttonBg});
break;
case SummaryButtonType.RETRY:
onPress = onRetry;
iconName = 'refresh';
text = {
id: t('invite.summary.try_again'),
defaultMessage: 'Try again',
};
break;
case SummaryButtonType.DONE:
default:
onPress = onClose;
text = {
id: t('invite.summary.done'),
defaultMessage: 'Done',
};
break;
}
onClose();
}, [error, onRetry, onClose]);
return (
<Button
containerStyle={[styleButtonBackground, {flexGrow: 1}]}
onPress={onPress}
testID={`invite.summary_button.${SummaryButtonType.RETRY}`}
>
<View style={styles.summaryButtonTextContainer}>
{iconName && (
<CompassIcon
name={iconName}
size={24}
style={styleButtonIcon}
/>
)}
<FormattedText
{...text}
style={styleButtonText}
/>
</View>
</Button>
);
}, [theme, locale, onClose, onRetry, onBack]);
return (
<View
@ -211,32 +273,14 @@ export default function Summary({
</ScrollView>
<View style={styles.footer}>
<View style={styles.summaryButtonContainer}>
<Button
containerStyle={styleButtonBackground}
onPress={handleOnPressButton}
testID='invite.summary_button'
>
{error ? (
<View style={styles.summaryButtonTextContainer}>
<CompassIcon
name='refresh'
size={24}
style={styles.summaryButtonIcon}
/>
<FormattedText
id='invite.summary.try_again'
defaultMessage='Try again'
style={styleButtonText}
/>
</View>
) : (
<FormattedText
id='invite.summary.done'
defaultMessage='Done'
style={styleButtonText}
/>
)}
</Button>
{error ? (
<>
{renderButton(SummaryButtonType.BACK)}
{renderButton(SummaryButtonType.RETRY)}
</>
) : (
renderButton(SummaryButtonType.DONE)
)}
</View>
</View>
</View>