Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update trips empty state view #48060

Merged
merged 27 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/animations/BankVault.lottie
daledah marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
Binary file added assets/animations/GenericEmptyState.lottie
daledah marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
Binary file added assets/animations/TripsEmptyState.lottie
Binary file not shown.
11 changes: 8 additions & 3 deletions src/components/EmptyStateComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ function EmptyStateComponent({
buttonAction,
containerStyles,
title,
titleStyles,
subtitle,
headerStyles,
headerContentStyles,
minModalHeight = 400,
canEmptyViewBeScrolled = false,
}: EmptyStateComponentProps) {
const styles = useThemeStyles();
const [videoAspectRatio, setVideoAspectRatio] = useState(VIDEO_ASPECT_RATIO);
Expand Down Expand Up @@ -78,7 +80,10 @@ function EmptyStateComponent({
}, [headerMedia, headerMediaType, headerContentStyles, videoAspectRatio, styles.emptyStateVideo]);

return (
<ScrollView contentContainerStyle={[styles.emptyStateScrollView, {minHeight: minModalHeight}, containerStyles]}>
<ScrollView
contentContainerStyle={[!canEmptyViewBeScrolled && styles.emptyStateScrollView, {minHeight: minModalHeight}, containerStyles]}
style={[canEmptyViewBeScrolled && {flex: 1}]}
daledah marked this conversation as resolved.
Show resolved Hide resolved
>
<View style={styles.skeletonBackground}>
<SkeletonComponent
gradientOpacityEnabled
Expand All @@ -89,8 +94,8 @@ function EmptyStateComponent({
<View style={styles.emptyStateContent}>
<View style={[styles.emptyStateHeader(headerMediaType === CONST.EMPTY_STATE_MEDIA.ILLUSTRATION), headerStyles]}>{HeaderComponent}</View>
<View style={styles.p8}>
<Text style={[styles.textAlignCenter, styles.textHeadlineH1, styles.mb2]}>{title}</Text>
<Text style={[styles.textAlignCenter, styles.textSupporting, styles.textNormal]}>{subtitle}</Text>
<Text style={[styles.textAlignCenter, styles.textHeadlineH1, styles.mb2, titleStyles]}>{title}</Text>
{typeof subtitle === 'string' ? <Text style={[styles.textAlignCenter, styles.textSupporting, styles.textNormal]}>{subtitle}</Text> : subtitle}
{!!buttonText && !!buttonAction && (
<Button
success
Expand Down
7 changes: 5 additions & 2 deletions src/components/EmptyStateComponent/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {ImageStyle} from 'expo-image';
import type {StyleProp, ViewStyle} from 'react-native';
import type {JSX} from 'react';
import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
import type {ValueOf} from 'type-fest';
import type DotLottieAnimation from '@components/LottieAnimations/types';
import type SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton';
Expand All @@ -13,14 +14,16 @@ type MediaTypes = ValueOf<typeof CONST.EMPTY_STATE_MEDIA>;
type SharedProps<T> = {
SkeletonComponent: ValidSkeletons;
title: string;
subtitle: string;
titleStyles?: StyleProp<TextStyle>;
subtitle: string | JSX.Element;
buttonText?: string;
buttonAction?: () => void;
containerStyles?: StyleProp<ViewStyle>;
headerStyles?: StyleProp<ViewStyle>;
headerMediaType: T;
headerContentStyles?: StyleProp<ViewStyle & ImageStyle>;
minModalHeight?: number;
canEmptyViewBeScrolled?: boolean;
};

type MediaType<HeaderMedia, T extends MediaTypes> = SharedProps<T> & {
Expand Down
5 changes: 5 additions & 0 deletions src/components/LottieAnimations/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ const DotLottieAnimations = {
w: 180,
h: 200,
},
TripsEmptyState: {
file: require<LottieViewProps['source']>('@assets/animations/TripsEmptyState.lottie'),
w: 335,
h: 220,
},
} satisfies Record<string, DotLottieAnimation>;

export default DotLottieAnimations;
4 changes: 3 additions & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2126,13 +2126,15 @@ export default {
travel: {
header: 'Book travel',
title: 'Travel smart',
subtitle: 'Use Expensify Travel to get the best travel offers and manage all your business expenses in one place.',
subtitle: 'Use Expensify Travel to get the best travel offers and manage all your business expenses in one place. ',
daledah marked this conversation as resolved.
Show resolved Hide resolved
features: {
saveMoney: 'Save money on your bookings',
alerts: 'Get realtime updates and alerts',
},
bookTravel: 'Book travel',
bookDemo: 'Book demo',
bookADemo: 'Book a demo',
toLearnMore: ' to learn more.',
termsAndConditions: {
header: 'Before we continue...',
title: 'Please read the Terms & Conditions for travel',
Expand Down
4 changes: 3 additions & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2157,13 +2157,15 @@ export default {
travel: {
header: 'Reservar viajes',
title: 'Viaja de forma inteligente',
subtitle: 'Utiliza Expensify Travel para obtener las mejores ofertas de viaje y gestionar todos los gastos de tu negocio en un solo lugar.',
subtitle: 'Utiliza Expensify Travel para obtener las mejores ofertas de viaje y gestionar todos los gastos de tu negocio en un solo lugar. ',
features: {
saveMoney: 'Ahorra dinero en tus reservas',
alerts: 'Obtén actualizaciones y alertas en tiempo real',
},
bookTravel: 'Reservar viajes',
bookDemo: 'Pedir demostración',
bookADemo: 'Reserva una demo',
toLearnMore: ' para obtener más información.',
termsAndConditions: {
header: 'Antes de continuar...',
title: 'Por favor, lee los Términos y condiciones para reservar viajes',
Expand Down
93 changes: 79 additions & 14 deletions src/pages/Search/EmptySearchView.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,114 @@
import React, {useMemo} from 'react';
import {Linking, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import EmptyStateComponent from '@components/EmptyStateComponent';
import type {FeatureListItem} from '@components/FeatureList';
import * as Illustrations from '@components/Icon/Illustrations';
import LottieAnimation from '@components/LottieAnimations';
import MenuItem from '@components/MenuItem';
import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {SearchDataTypes} from '@src/types/onyx/SearchResults';

type EmptySearchViewProps = {
type: SearchDataTypes;
};

type TripsFeaturesProps = FeatureListItem & {
title: string;
};

function EmptySearchView({type}: EmptySearchViewProps) {
const theme = useTheme();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const styles = useThemeStyles();

const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);

const subtitleComponent = useMemo(() => {
const tripsFeatures: TripsFeaturesProps[] = [
{
icon: Illustrations.PiggyBank,
translationKey: 'travel.features.saveMoney',
title: translate('travel.features.saveMoney'),
daledah marked this conversation as resolved.
Show resolved Hide resolved
},
{
icon: Illustrations.Alert,
translationKey: 'travel.features.alerts',
title: translate('travel.features.alerts'),
},
];

return (
<>
<Text style={[styles.textSupporting, styles.textNormal]}>
{translate('travel.subtitle')}
<TextLink
onPress={() => {
Linking.openURL(CONST.BOOK_TRAVEL_DEMO_URL);
}}
>
{translate('travel.bookADemo')}
</TextLink>
{translate('travel.toLearnMore')}
</Text>
<View style={[styles.flex1, styles.flexRow, styles.flexWrap, styles.rowGap4, styles.pt4, styles.pl1]}>
{tripsFeatures.map((tripsFeature) => (
daledah marked this conversation as resolved.
Show resolved Hide resolved
<View
key={tripsFeature.translationKey}
style={styles.w100}
>
<MenuItem
title={tripsFeature.title}
icon={tripsFeature.icon}
iconWidth={variables.menuIconSize}
iconHeight={variables.menuIconSize}
interactive={false}
displayInDefaultIconColor
wrapperStyle={[styles.p0, styles.cursorAuto]}
containerStyle={[styles.m0, styles.wAuto]}
numberOfLinesTitle={0}
/>
</View>
))}
</View>
</>
);
}, [styles, translate]);

const content = useMemo(() => {
switch (type) {
case CONST.SEARCH.DATA_TYPES.TRIP:
return {
headerMedia: Illustrations.EmptyStateTravel,
headerMedia: LottieAnimation.TripsEmptyState,
headerMediaType: CONST.EMPTY_STATE_MEDIA.ANIMATION,
headerStyles: StyleUtils.getBackgroundColorStyle(theme.travelBG),
headerContentStyles: StyleUtils.getWidthAndHeightStyle(variables.w191, variables.h172),
title: translate('search.searchResults.emptyTripResults.title'),
subtitle: translate('search.searchResults.emptyTripResults.subtitle'),
headerContentStyles: StyleUtils.getWidthAndHeightStyle(335, 220),
title: translate('travel.title'),
titleStyles: {...styles.textAlignLeft},
subtitle: subtitleComponent,
buttonText: translate('search.searchResults.emptyTripResults.buttonText'),
buttonAction: () => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS),
buttonAction: () => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(activePolicyID ?? '-1')),
daledah marked this conversation as resolved.
Show resolved Hide resolved
canEmptyViewBeScrolled: true,
};
case CONST.SEARCH.DATA_TYPES.CHAT:
case CONST.SEARCH.DATA_TYPES.EXPENSE:
case CONST.SEARCH.DATA_TYPES.INVOICE:
default:
return {
headerMedia: Illustrations.EmptyState,
headerMediaType: CONST.EMPTY_STATE_MEDIA.ILLUSTRATION,
headerStyles: StyleUtils.getBackgroundColorStyle(theme.emptyFolderBG),
headerContentStyles: StyleUtils.getWidthAndHeightStyle(variables.w184, variables.h112),
title: translate('search.searchResults.emptyResults.title'),
Expand All @@ -46,19 +117,13 @@ function EmptySearchView({type}: EmptySearchViewProps) {
buttonAction: undefined,
};
}
}, [type, StyleUtils, translate, theme]);
}, [type, StyleUtils, translate, theme, styles, subtitleComponent, activePolicyID]);

return (
<EmptyStateComponent
// eslint-disable-next-line react/jsx-props-no-spreading
daledah marked this conversation as resolved.
Show resolved Hide resolved
{...content}
SkeletonComponent={SearchRowSkeleton}
headerMediaType={CONST.EMPTY_STATE_MEDIA.ILLUSTRATION}
headerMedia={content.headerMedia}
headerStyles={content.headerStyles}
headerContentStyles={content.headerContentStyles}
title={content.title}
subtitle={content.subtitle}
buttonText={content.buttonText}
buttonAction={content.buttonAction}
/>
);
}
Expand Down
Loading