[Gekidou] Handle gracefully teams or channels are fetch errors out (#5913)

This commit is contained in:
Elias Nahum
2022-01-27 01:25:30 -03:00
committed by GitHub
parent dec30cb206
commit be63e3c780
18 changed files with 294 additions and 847 deletions

View File

@@ -193,587 +193,147 @@ exports[`components/channel_list should match snapshot 1`] = `
style={
Object {
"alignItems": "center",
"backgroundColor": "rgba(255,255,255,0.12)",
"borderRadius": 8,
"flex": 1,
"flexDirection": "row",
"height": 40,
"justifyContent": "flex-start",
"marginVertical": 20,
"maxHeight": 40,
"padding": 8,
"width": "100%",
"justifyContent": "center",
"padding": 20,
}
}
>
<Icon
name="magnify"
<View
style={
Object {
"color": "rgba(255,255,255,0.72)",
"fontSize": 24,
"width": 24,
"alignItems": "center",
"backgroundColor": "rgba(255,255,255,0.08)",
"borderRadius": 60,
"height": 120,
"justifyContent": "center",
"width": 120,
}
}
/>
<TextInput
placeholder="Find Channels"
placeholderTextColor="rgba(255,255,255,0.72)"
>
<Icon
name="alert-circle-outline"
style={
Object {
"color": "rgba(255,255,255,0.48)",
"fontSize": 72,
"lineHeight": 72,
}
}
/>
</View>
<Text
style={
Array [
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"textAlignVertical": "center",
},
],
Object {
"alignContent": "center",
"alignItems": "center",
"fontFamily": "Metropolis-SemiBold",
"fontSize": 20,
"fontWeight": "600",
"lineHeight": 28,
},
Object {
"color": "#ffffff",
"flex": 1,
"height": 40,
"marginLeft": 5,
"marginTop": -2,
"marginTop": 20,
"textAlign": "center",
},
]
}
/>
</View>
<RCTScrollView
ListHeaderComponent={[Function]}
data={
Array [
Object {
"channels": Array [
Object {
"id": "1",
"name": "Just a channel",
},
Object {
"highlight": true,
"id": "2",
"name": "A Highlighted Channel!!!",
},
Object {
"id": "3",
"name": "And a longer channel name.",
},
],
"id": "1",
"title": "My first Category",
},
Object {
"channels": Array [
Object {
"id": "1",
"name": "Just a channel",
},
Object {
"highlight": true,
"id": "2",
"name": "A Highlighted Channel!!!",
},
Object {
"id": "3",
"name": "And a longer channel name.",
},
],
"id": "2",
"title": "Another Cat",
},
]
}
getItem={[Function]}
getItemCount={[Function]}
keyExtractor={[Function]}
onContentSizeChange={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
removeClippedSubviews={false}
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={Array []}
style={
Object {
"flex": 1,
>
Couldn't load
</Text>
<Text
style={
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"color": "#ffffff",
"marginTop": 4,
"textAlign": "center",
},
]
}
}
viewabilityConfigCallbackPairs={Array []}
>
<View>
>
There was a problem loading content for this server.
</Text>
<View
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
>
<View
onLayout={[Function]}
>
<View
onMoveShouldSetResponder={[Function]}
onMoveShouldSetResponderCapture={[Function]}
onResponderEnd={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderReject={[Function]}
onResponderRelease={[Function]}
onResponderStart={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
onStartShouldSetResponderCapture={[Function]}
>
<View
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
>
<View
style={
Object {
"display": "flex",
"flexDirection": "row",
}
}
>
<Icon
name="message-text-outline"
style={
Object {
"color": "#ffffff",
"fontSize": 24,
"lineHeight": 28,
}
}
/>
<Text
style={
Array [
Array [
Object {
"fontFamily": "OpenSans-SemiBold",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
},
],
Object {
"color": "#ffffff",
"paddingLeft": 12,
},
]
}
>
Threads
</Text>
</View>
</View>
</View>
</View>
<View
onLayout={[Function]}
style={null}
>
<View
style={
accessible={true}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"marginTop": 12,
"paddingVertical": 8,
}
}
>
<Text
style={
Object {
"color": "rgba(255,255,255,0.64)",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 12,
"fontWeight": "600",
"lineHeight": 16,
}
}
>
MY FIRST CATEGORY
</Text>
</View>
<View
data={
"marginTop": 24,
},
Array [
Object {
"id": "1",
"name": "Just a channel",
"alignItems": "center",
"borderRadius": 4,
"flex": 0,
"justifyContent": "center",
},
Object {
"highlight": true,
"id": "2",
"name": "A Highlighted Channel!!!",
"height": 48,
"paddingHorizontal": 24,
"paddingVertical": 14,
},
Object {
"id": "3",
"name": "And a longer channel name.",
"backgroundColor": "#ffffff",
},
],
]
}
>
<Text
style={
Array [
Object {
"alignItems": "center",
"fontFamily": "OpenSans-SemiBold",
"fontWeight": "600",
"justifyContent": "center",
"padding": 1,
"textAlignVertical": "center",
},
Object {
"fontSize": 16,
"lineHeight": 16,
"marginTop": 1,
},
Object {
"color": "#1c58d9",
},
]
}
getItem={[Function]}
getItemCount={[Function]}
keyExtractor={[Function]}
onContentSizeChange={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
removeClippedSubviews={false}
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={Array []}
viewabilityConfigCallbackPairs={Array []}
>
<View
onLayout={[Function]}
style={null}
>
<View
style={
Object {
"flexDirection": "row",
"paddingVertical": 4,
}
}
>
<Icon
name="globe"
style={
Object {
"color": "rgba(255,255,255,0.72)",
"fontSize": 24,
"lineHeight": 28,
}
}
/>
<Text
style={
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"marginTop": 1,
"paddingLeft": 12,
},
undefined,
]
}
>
Just a channel
</Text>
</View>
</View>
<View
onLayout={[Function]}
style={null}
>
<View
style={
Object {
"flexDirection": "row",
"paddingVertical": 4,
}
}
>
<Icon
name="globe"
style={
Object {
"color": "rgba(255,255,255,0.72)",
"fontSize": 24,
"lineHeight": 28,
}
}
/>
<Text
style={
Array [
Object {
"fontFamily": "OpenSans-SemiBold",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"marginTop": 1,
"paddingLeft": 12,
},
Object {
"color": "#ffffff",
},
]
}
>
A Highlighted Channel!!!
</Text>
</View>
</View>
<View
onLayout={[Function]}
style={null}
>
<View
style={
Object {
"flexDirection": "row",
"paddingVertical": 4,
}
}
>
<Icon
name="globe"
style={
Object {
"color": "rgba(255,255,255,0.72)",
"fontSize": 24,
"lineHeight": 28,
}
}
/>
<Text
style={
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"marginTop": 1,
"paddingLeft": 12,
},
undefined,
]
}
>
And a longer channel name.
</Text>
</View>
</View>
</View>
</View>
<View
onLayout={[Function]}
style={null}
>
<View
style={
Object {
"marginTop": 12,
"paddingVertical": 8,
}
}
>
<Text
style={
Object {
"color": "rgba(255,255,255,0.64)",
"fontFamily": "OpenSans-SemiBold",
"fontSize": 12,
"fontWeight": "600",
"lineHeight": 16,
}
}
>
ANOTHER CAT
</Text>
</View>
<View
data={
Array [
Object {
"id": "1",
"name": "Just a channel",
},
Object {
"highlight": true,
"id": "2",
"name": "A Highlighted Channel!!!",
},
Object {
"id": "3",
"name": "And a longer channel name.",
},
]
}
getItem={[Function]}
getItemCount={[Function]}
keyExtractor={[Function]}
onContentSizeChange={[Function]}
onLayout={[Function]}
onMomentumScrollBegin={[Function]}
onMomentumScrollEnd={[Function]}
onScroll={[Function]}
onScrollBeginDrag={[Function]}
onScrollEndDrag={[Function]}
removeClippedSubviews={false}
renderItem={[Function]}
scrollEventThrottle={50}
stickyHeaderIndices={Array []}
viewabilityConfigCallbackPairs={Array []}
>
<View
onLayout={[Function]}
style={null}
>
<View
style={
Object {
"flexDirection": "row",
"paddingVertical": 4,
}
}
>
<Icon
name="globe"
style={
Object {
"color": "rgba(255,255,255,0.72)",
"fontSize": 24,
"lineHeight": 28,
}
}
/>
<Text
style={
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"marginTop": 1,
"paddingLeft": 12,
},
undefined,
]
}
>
Just a channel
</Text>
</View>
</View>
<View
onLayout={[Function]}
style={null}
>
<View
style={
Object {
"flexDirection": "row",
"paddingVertical": 4,
}
}
>
<Icon
name="globe"
style={
Object {
"color": "rgba(255,255,255,0.72)",
"fontSize": 24,
"lineHeight": 28,
}
}
/>
<Text
style={
Array [
Object {
"fontFamily": "OpenSans-SemiBold",
"fontSize": 16,
"fontWeight": "600",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"marginTop": 1,
"paddingLeft": 12,
},
Object {
"color": "#ffffff",
},
]
}
>
A Highlighted Channel!!!
</Text>
</View>
</View>
<View
onLayout={[Function]}
style={null}
>
<View
style={
Object {
"flexDirection": "row",
"paddingVertical": 4,
}
}
>
<Icon
name="globe"
style={
Object {
"color": "rgba(255,255,255,0.72)",
"fontSize": 24,
"lineHeight": 28,
}
}
/>
<Text
style={
Array [
Object {
"fontFamily": "OpenSans",
"fontSize": 16,
"fontWeight": "400",
"lineHeight": 24,
},
Object {
"color": "rgba(255,255,255,0.72)",
"marginTop": 1,
"paddingLeft": 12,
},
undefined,
]
}
>
And a longer channel name.
</Text>
</View>
</View>
</View>
Retry
</Text>
</View>
</View>
</RCTScrollView>
</View>
</View>
`;

View File

@@ -67,6 +67,7 @@ const ChannelListHeader = ({displayName, iconPad}: Props) => {
return (
<Animated.View style={animatedStyle}>
{Boolean(displayName) &&
<View style={styles.headerRow}>
<View style={styles.headerRow}>
<Text style={styles.headingStyles}>
@@ -86,6 +87,7 @@ const ChannelListHeader = ({displayName, iconPad}: Props) => {
/>
</TouchableWithFeedback>
</View>
}
<Text style={styles.subHeadingStyles}>
{serverDisplayName}
</Text>

View File

@@ -3,7 +3,7 @@
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {catchError, of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
@@ -15,9 +15,10 @@ import type {WithDatabaseArgs} from '@typings/database/database';
import type SystemModel from '@typings/database/models/servers/system';
import type TeamModel from '@typings/database/models/servers/team';
const withCurrentTeam = withObservables([], ({database}: WithDatabaseArgs) => {
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
const team = database.get<SystemModel>(SYSTEM).findAndObserve(SYSTEM_IDENTIFIERS.CURRENT_TEAM_ID).pipe(
switchMap((id) => database.get<TeamModel>(TEAM).findAndObserve(id.value)),
catchError(() => of$({displayName: ''})),
);
return {
@@ -27,4 +28,4 @@ const withCurrentTeam = withObservables([], ({database}: WithDatabaseArgs) => {
};
});
export default withDatabase(withCurrentTeam(ChannelListHeader));
export default withDatabase(enhanced(ChannelListHeader));

View File

@@ -11,7 +11,8 @@ import {makeStyleSheetFromTheme} from '@utils/theme';
import Categories from './categories';
import ChannelListHeader from './header';
import LoadingError from './loading_error';
import LoadChannelsError from './load_channels_error';
import LoadTeamsError from './load_teams_error';
import SearchField from './search';
// import Loading from '@components/loading';
@@ -38,12 +39,13 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
}));
type ChannelListProps = {
currentTeamId?: string;
iconPad?: boolean;
isTablet: boolean;
teamsCount: number;
}
const ChannelList = ({iconPad, isTablet, teamsCount}: ChannelListProps) => {
const ChannelList = ({currentTeamId, iconPad, isTablet, teamsCount}: ChannelListProps) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const tabletWidth = useSharedValue(TABLET_SIDEBAR_WIDTH);
@@ -64,6 +66,20 @@ const ChannelList = ({iconPad, isTablet, teamsCount}: ChannelListProps) => {
}, [isTablet, teamsCount]);
const [showCats, setShowCats] = useState<boolean>(true);
let content;
if (!currentTeamId) {
content = (<LoadTeamsError/>);
} else if (showCats) {
content = (
<>
<SearchField/>
<Categories categories={categories}/>
</>
);
} else {
content = (<LoadChannelsError teamId={currentTeamId}/>);
}
return (
<Animated.View style={[styles.container, tabletStyle]}>
<TouchableOpacity onPress={() => setShowCats(!showCats)}>
@@ -71,15 +87,7 @@ const ChannelList = ({iconPad, isTablet, teamsCount}: ChannelListProps) => {
iconPad={iconPad}
/>
</TouchableOpacity>
{showCats && (
<>
<SearchField/>
<Categories categories={categories}/>
</>
)}
{/* <Loading/> */}
{!showCats && (<LoadingError/>)}
{content}
</Animated.View>
);
};

View File

@@ -0,0 +1,27 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {MM_TABLES} from '@constants/database';
const {SERVER: {TEAM}} = MM_TABLES;
import LoadChannelsError from './load_channel_error';
import type {WithDatabaseArgs} from '@typings/database/database';
import type TeamModel from '@typings/database/models/servers/team';
const enhanced = withObservables(['teamId'], ({database, teamId}: {teamId: string} & WithDatabaseArgs) => {
const team = database.get<TeamModel>(TEAM).findAndObserve(teamId);
return {
teamDisplayName: team.pipe(
switchMap((t) => of$(t.displayName)),
),
};
});
export default withDatabase(enhanced(LoadChannelsError));

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useState} from 'react';
import {useIntl} from 'react-intl';
import {retryInitialChannel} from '@actions/remote/retry';
import LoadingError from '@components/channel_list/loading_error';
import {useServerUrl} from '@context/server';
type Props = {
teamDisplayName: string;
teamId: string;
}
const LoadChannelsError = ({teamDisplayName, teamId}: Props) => {
const {formatMessage} = useIntl();
const serverUrl = useServerUrl();
const [loading, setLoading] = useState(false);
const onRetryTeams = useCallback(async () => {
setLoading(true);
const {error} = await retryInitialChannel(serverUrl, teamId);
if (error) {
setLoading(false);
}
}, [teamId]);
return (
<LoadingError
loading={loading}
message={formatMessage({id: 'load_channels_error.message', defaultMessage: 'There was a problem loading content for this team.'})}
onRetry={onRetryTeams}
title={formatMessage({id: 'load_channels_error.title', defaultMessage: "Couldn't load {teamDisplayName}"}, {teamDisplayName})}
/>
);
};
export default LoadChannelsError;

View File

@@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback, useState} from 'react';
import {useIntl} from 'react-intl';
import {retryInitialTeamAndChannel} from '@actions/remote/retry';
import LoadingError from '@components/channel_list/loading_error';
import {useServerDisplayName, useServerUrl} from '@context/server';
const LoadTeamsError = () => {
const {formatMessage} = useIntl();
const serverUrl = useServerUrl();
const serverName = useServerDisplayName();
const [loading, setLoading] = useState(false);
const onRetryTeams = useCallback(async () => {
setLoading(true);
const {error} = await retryInitialTeamAndChannel(serverUrl);
if (error) {
setLoading(false);
}
}, []);
return (
<LoadingError
loading={loading}
message={formatMessage({id: 'load_teams_error.message', defaultMessage: 'There was a problem loading content for this server.'})}
onRetry={onRetryTeams}
title={formatMessage({id: 'load_teams_error.title', defaultMessage: "Couldn't load {serverName}"}, {serverName})}
/>
);
};
export default LoadTeamsError;

View File

@@ -51,7 +51,7 @@ exports[`Loading Error should match snapshot 1`] = `
]
}
>
Couldnt load Staff
Error title
</Text>
<Text
style={
@@ -70,7 +70,7 @@ exports[`Loading Error should match snapshot 1`] = `
]
}
>
There was a problem loading the content for this team.
Error description
</Text>
<View
onMoveShouldSetResponder={[Function]}
@@ -88,7 +88,7 @@ exports[`Loading Error should match snapshot 1`] = `
>
<View
accessible={true}
focusable={false}
focusable={true}
onClick={[Function]}
onResponderGrant={[Function]}
onResponderMove={[Function]}

View File

@@ -9,7 +9,12 @@ import ErrorComponent from './index';
test('Loading Error should match snapshot', () => {
const {toJSON} = renderWithIntlAndTheme(
<ErrorComponent/>,
<ErrorComponent
loading={false}
message='Error description'
onRetry={() => true}
title='Error title'
/>,
);
expect(toJSON()).toMatchSnapshot();

View File

@@ -1,15 +1,24 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import React, {useMemo} from 'react';
import {Text, View} from 'react-native';
import CompassIcon from '@components/compass_icon';
import Loading from '@components/loading';
import TouchableWithFeedback from '@components/touchable_with_feedback';
import {useTheme} from '@context/theme';
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import {typography} from '@utils/typography';
type Props = {
loading: boolean;
message: string;
onRetry: () => void;
title: string;
}
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
container: {
flex: 1,
@@ -42,9 +51,21 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
},
}));
const LoadingError = () => {
const LoadingError = ({loading, message, onRetry, title}: Props) => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const buttonStyle = useMemo(() => {
return [{marginTop: 24}, buttonBackgroundStyle(theme, 'lg', 'primary', 'inverted')];
}, [theme]);
if (loading) {
return (
<Loading
containerStyle={styles.container}
color={theme.buttonBg}
/>
);
}
return (
<View style={styles.container}>
@@ -55,12 +76,15 @@ const LoadingError = () => {
/>
</View>
<Text style={[typography('Heading', 400), styles.header]}>
{'Couldnt load Staff'}
{title}
</Text>
<Text style={[typography('Body', 200), styles.body]}>
{'There was a problem loading the content for this team.'}
{message}
</Text>
<TouchableWithFeedback style={[{marginTop: 24}, buttonBackgroundStyle(theme, 'lg', 'primary', 'inverted')]}>
<TouchableWithFeedback
style={buttonStyle}
onPress={onRetry}
>
<Text style={buttonTextStyle(theme, 'lg', 'primary', 'inverted')}>{'Retry'}</Text>
</TouchableWithFeedback>
</View>

View File

@@ -1,36 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {View} from 'react-native';
import Svg, {Path} from 'react-native-svg';
type CloudSvgProps = {
color: string;
height: number;
width: number;
}
const CloudSvg = ({color, height, width}: CloudSvgProps) => {
return (
<View
style={{height, width, alignItems: 'flex-start'}}
testID='failed_network_action.cloud_icon'
>
<Svg
width={width}
height={height}
viewBox='0 0 72 47'
>
<Path
d='M58.464,19.072c0,-5.181 -1.773,-9.599 -5.316,-13.249c-3.545,-3.649 -7.854,-5.474 -12.932,-5.474c-3.597,0 -6.902,0.979 -9.917,2.935c-3.014,1.959 -5.263,4.523 -6.743,7.696c-1.483,-0.739 -2.856,-1.111 -4.126,-1.111c-2.328,0 -4.363,0.769 -6.109,2.301c-1.745,1.535 -2.831,3.466 -3.252,5.792c-2.856,0.952 -5.185,2.672 -6.982,5.156c-1.8,2.487 -2.697,5.316 -2.697,8.489c0,3.915 1.4,7.299 4.204,10.155c2.802,2.857 6.161,4.285 10.076,4.285l43.794,0c3.595,0 6.664,-1.295 9.203,-3.888c2.538,-2.591 3.808,-5.685 3.808,-9.282c0,-3.702 -1.27,-6.848 -3.808,-9.441c-2.539,-2.591 -5.608,-3.888 -9.203,-3.888l0,-0.476Zm-31.294,16.424l17.17,0c-0.842,-1.62 -2.02,-2.92 -3.535,-3.898c-1.515,-0.977 -3.198,-1.467 -5.05,-1.467c-1.852,0 -3.535,0.49 -5.05,1.467c-1.515,0.978 -2.693,2.278 -3.535,3.898l0,0Zm17.338,-12.407c0,-0.782 -0.252,-1.411 -0.757,-1.886c-0.505,-0.474 -1.124,-0.713 -1.852,-0.713c-0.73,0 -1.347,0.239 -1.852,0.713c-0.505,0.475 -0.757,1.104 -0.757,1.886c0,0.783 0.252,1.412 0.757,1.886c0.505,0.476 1.122,0.713 1.852,0.713c0.728,0 1.347,-0.237 1.852,-0.713c0.505,-0.474 0.757,-1.103 0.757,-1.886Zm-12.288,0c0,-0.782 -0.253,-1.411 -0.758,-1.886c-0.505,-0.474 -1.123,-0.713 -1.851,-0.713c-0.73,0 -1.347,0.239 -1.852,0.713c-0.505,0.475 -0.757,1.104 -0.757,1.886c0,0.783 0.252,1.412 0.757,1.886c0.505,0.476 1.122,0.713 1.852,0.713c0.728,0 1.346,-0.237 1.851,-0.713c0.505,-0.474 0.758,-1.103 0.758,-1.886Z'
fillRule='evenodd'
strokeLinejoin='round'
fill={color}
/>
</Svg>
</View>
);
};
export default CloudSvg;

View File

@@ -1,94 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {useIntl} from 'react-intl';
import {Text, View} from 'react-native';
import Button from 'react-native-button';
import {useTheme} from '@context/theme';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
import CloudSvg from './cloud_svg';
type FailedActionProps = {
action?: string;
message: string;
title: string;
onAction: () => void;
}
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
return {
container: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
paddingHorizontal: 20,
paddingBottom: 15,
},
title: {
color: changeOpacity(theme.centerChannelColor, 0.8),
fontSize: 20,
fontFamily: 'OpenSans-SemiBold',
marginBottom: 15,
marginTop: 10,
},
description: {
color: changeOpacity(theme.centerChannelColor, 0.4),
fontSize: 17,
lineHeight: 25,
textAlign: 'center',
},
link: {
color: theme.buttonColor,
fontSize: 15,
},
buttonContainer: {
backgroundColor: theme.buttonBg,
borderRadius: 5,
height: 42,
justifyContent: 'center',
marginTop: 20,
paddingHorizontal: 12,
},
};
});
const FailedAction = ({action, message, title, onAction}: FailedActionProps) => {
const intl = useIntl();
const theme = useTheme();
const style = getStyleFromTheme(theme);
const text = action || intl.formatMessage({id: 'failed_action.try_again', defaultMessage: 'Try again'});
return (
<View style={style.container}>
<CloudSvg
color={changeOpacity(theme.centerChannelColor, 0.15)}
height={76}
width={76}
/>
<Text
style={style.title}
testID='error_title'
>
{title}
</Text>
<Text
style={style.description}
testID='error_text'
>
{message}
</Text>
<Button
containerStyle={style.buttonContainer}
onPress={onAction}
>
<Text style={style.link}>{text}</Text>
</Button>
</View>
);
};
export default FailedAction;