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 26 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
14 changes: 9 additions & 5 deletions src/components/EmptyStateComponent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function EmptyStateComponent({
buttonAction,
containerStyles,
title,
titleStyles,
subtitle,
headerStyles,
headerContentStyles,
Expand All @@ -30,7 +31,7 @@ function EmptyStateComponent({
}: EmptyStateComponentProps) {
const styles = useThemeStyles();
const [videoAspectRatio, setVideoAspectRatio] = useState(VIDEO_ASPECT_RATIO);
const {isSmallScreenWidth} = useResponsiveLayout();
const {shouldUseNarrowLayout} = useResponsiveLayout();

const setAspectRatio = (event: VideoReadyForDisplayEvent | VideoLoadedEventType | undefined) => {
if (!event) {
Expand Down Expand Up @@ -82,7 +83,10 @@ function EmptyStateComponent({
}, [headerMedia, headerMediaType, headerContentStyles, videoAspectRatio, styles.emptyStateVideo, lottieWebViewStyles]);

return (
<ScrollView contentContainerStyle={[styles.emptyStateScrollView, {minHeight: minModalHeight}, containerStyles]}>
<ScrollView
contentContainerStyle={[{minHeight: minModalHeight}, styles.flexGrow1, styles.flexShrink0, containerStyles]}
style={styles.flex1}
>
<View style={styles.skeletonBackground}>
<SkeletonComponent
gradientOpacityEnabled
Expand All @@ -92,9 +96,9 @@ function EmptyStateComponent({
<View style={styles.emptyStateForeground}>
<View style={styles.emptyStateContent}>
<View style={[styles.emptyStateHeader(headerMediaType === CONST.EMPTY_STATE_MEDIA.ILLUSTRATION), headerStyles]}>{HeaderComponent}</View>
<View style={isSmallScreenWidth ? styles.p5 : styles.p8}>
<Text style={[styles.textAlignCenter, styles.textHeadlineH1, styles.mb2]}>{title}</Text>
<Text style={[styles.textAlignCenter, styles.textSupporting, styles.textNormal]}>{subtitle}</Text>
<View style={shouldUseNarrowLayout ? styles.p5 : styles.p8}>
<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
3 changes: 2 additions & 1 deletion src/components/EmptyStateComponent/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {ImageStyle} from 'expo-image';
import type {StyleProp, ViewStyle} from 'react-native';
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,6 +13,7 @@ type MediaTypes = ValueOf<typeof CONST.EMPTY_STATE_MEDIA>;
type SharedProps<T> = {
SkeletonComponent: ValidSkeletons;
title: string;
titleStyles?: StyleProp<TextStyle>;
subtitle: string | React.ReactNode;
buttonText?: string;
buttonAction?: () => void;
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2140,6 +2140,8 @@ export default {
},
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
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2170,6 +2170,8 @@ export default {
},
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
49 changes: 48 additions & 1 deletion src/libs/TripReservationUtils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,35 @@
import {Str} from 'expensify-common';
import type {Dispatch, SetStateAction} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import * as Expensicons from '@src/components/Icon/Expensicons';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {TravelSettings} from '@src/types/onyx';
import type {Reservation, ReservationType} from '@src/types/onyx/Transaction';
import type Transaction from '@src/types/onyx/Transaction';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type IconAsset from '@src/types/utils/IconAsset';
import * as Link from './actions/Link';
import Navigation from './Navigation/Navigation';

let travelSettings: OnyxEntry<TravelSettings>;
Onyx.connect({
key: ONYXKEYS.NVP_TRAVEL_SETTINGS,
callback: (val) => {
travelSettings = val;
},
});

let activePolicyID: OnyxEntry<string>;
Onyx.connect({
key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
callback: (val) => {
activePolicyID = val;
},
});

function getTripReservationIcon(reservationType: ReservationType): IconAsset {
switch (reservationType) {
Expand Down Expand Up @@ -38,4 +65,24 @@ function getTripEReceiptIcon(transaction?: Transaction): IconAsset | undefined {
}
}

export {getTripReservationIcon, getReservationsFromTripTransactions, getTripEReceiptIcon};
function bookATrip(translate: LocaleContextProps['translate'], primaryLogin: string, setCtaErrorMessage: Dispatch<SetStateAction<string>>, ctaErrorMessage = ''): void {
if (Str.isSMSLogin(primaryLogin)) {
setCtaErrorMessage(translate('travel.phoneError'));
return;
}
if (isEmptyObject(travelSettings)) {
Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(activePolicyID ?? '-1', Navigation.getActiveRoute()));
return;
}
if (!travelSettings?.hasAcceptedTerms) {
Navigation.navigate(ROUTES.TRAVEL_TCS);
return;
}
if (ctaErrorMessage) {
setCtaErrorMessage('');
}
Link.openTravelDotLink(activePolicyID)?.catch(() => {
setCtaErrorMessage(translate('travel.errorMessage'));
});
}
export {getTripReservationIcon, getReservationsFromTripTransactions, getTripEReceiptIcon, bookATrip};
87 changes: 79 additions & 8 deletions src/pages/Search/EmptySearchView.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,106 @@
import React, {useMemo} from 'react';
import React, {useMemo, useState} from 'react';
import {Linking, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import DotIndicatorMessage from '@components/DotIndicatorMessage';
import EmptyStateComponent from '@components/EmptyStateComponent';
import type {FeatureListItem} from '@components/FeatureList';
import * as Illustrations from '@components/Icon/Illustrations';
import LottieAnimations 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 * as TripsResevationUtils from '@libs/TripReservationUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import ONYXKEYS from '@src/ONYXKEYS';
import type {SearchDataTypes} from '@src/types/onyx/SearchResults';

type EmptySearchViewProps = {
type: SearchDataTypes;
};

const tripsFeatures: FeatureListItem[] = [
{
icon: Illustrations.PiggyBank,
translationKey: 'travel.features.saveMoney',
},
{
icon: Illustrations.Alert,
translationKey: 'travel.features.alerts',
},
];

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

const [primaryLogin = ''] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => account?.primaryLogin});
const [ctaErrorMessage, setCtaErrorMessage] = useState('');

const subtitleComponent = useMemo(() => {
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) => (
<View
daledah marked this conversation as resolved.
Show resolved Hide resolved
key={tripsFeature.translationKey}
style={styles.w100}
>
<MenuItem
title={translate(tripsFeature.translationKey)}
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>
{ctaErrorMessage && (
<DotIndicatorMessage
style={styles.mt1}
messages={{error: ctaErrorMessage}}
type="error"
/>
)}
</>
);
}, [styles, translate, ctaErrorMessage]);

const content = useMemo(() => {
switch (type) {
case CONST.SEARCH.DATA_TYPES.TRIP:
return {
headerMedia: LottieAnimations.TripsEmptyState,
headerStyles: [StyleUtils.getBackgroundColorStyle(theme.travelBG), styles.w100],
title: translate('search.searchResults.emptyTripResults.title'),
subtitle: translate('search.searchResults.emptyTripResults.subtitle'),
headerStyles: StyleUtils.getBackgroundColorStyle(theme.travelBG),
headerContentStyles: StyleUtils.getWidthAndHeightStyle(375, 240),
title: translate('travel.title'),
titleStyles: {...styles.textAlignLeft},
subtitle: subtitleComponent,
buttonText: translate('search.searchResults.emptyTripResults.buttonText'),
buttonAction: () => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS),
buttonAction: () => TripsResevationUtils.bookATrip(translate, primaryLogin ?? '', setCtaErrorMessage, ctaErrorMessage),
};
case CONST.SEARCH.DATA_TYPES.CHAT:
case CONST.SEARCH.DATA_TYPES.EXPENSE:
Expand All @@ -46,7 +116,7 @@ function EmptySearchView({type}: EmptySearchViewProps) {
headerContentStyles: styles.emptyStateFolderWebStyles,
};
}
}, [type, StyleUtils, translate, theme, styles.w100, styles.emptyStateFolderWebStyles]);
}, [type, StyleUtils, translate, theme, styles, subtitleComponent, primaryLogin, ctaErrorMessage]);

return (
<EmptyStateComponent
Expand All @@ -55,6 +125,7 @@ function EmptySearchView({type}: EmptySearchViewProps) {
headerMedia={content.headerMedia}
headerStyles={[content.headerStyles, styles.emptyStateCardIllustrationContainer]}
title={content.title}
titleStyles={content.titleStyles}
subtitle={content.subtitle}
buttonText={content.buttonText}
buttonAction={content.buttonAction}
Expand Down
28 changes: 2 additions & 26 deletions src/pages/Travel/ManageTrips.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {Str} from 'expensify-common';
import React, {useState} from 'react';
import {Linking, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
Expand All @@ -12,12 +11,10 @@ import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as TripsResevationUtils from '@libs/TripReservationUtils';
import colors from '@styles/theme/colors';
import * as Link from '@userActions/Link';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

const tripsFeatures: FeatureListItem[] = [
Expand All @@ -35,7 +32,6 @@ function ManageTrips() {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {translate} = useLocalize();
const [travelSettings] = useOnyx(ONYXKEYS.NVP_TRAVEL_SETTINGS);
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const policy = usePolicy(activePolicyID);
Expand All @@ -46,9 +42,6 @@ function ManageTrips() {
return <FullScreenLoadingIndicator />;
}

const hasAcceptedTravelTerms = travelSettings?.hasAcceptedTerms;
const hasPolicyAddress = !isEmptyObject(policy?.address);

const navigateToBookTravelDemo = () => {
Linking.openURL(CONST.BOOK_TRAVEL_DEMO_URL);
};
Expand All @@ -63,24 +56,7 @@ function ManageTrips() {
ctaText={translate('travel.bookTravel')}
ctaAccessibilityLabel={translate('travel.bookTravel')}
onCtaPress={() => {
if (Str.isSMSLogin(account?.primaryLogin ?? '')) {
setCtaErrorMessage(translate('travel.phoneError'));
return;
}
if (!hasPolicyAddress) {
Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(activePolicyID ?? '-1', Navigation.getActiveRoute()));
return;
}
if (!hasAcceptedTravelTerms) {
Navigation.navigate(ROUTES.TRAVEL_TCS);
return;
}
if (ctaErrorMessage) {
setCtaErrorMessage('');
}
Link.openTravelDotLink(activePolicyID)?.catch(() => {
setCtaErrorMessage(translate('travel.errorMessage'));
});
TripsResevationUtils.bookATrip(translate, account?.primaryLogin ?? '', setCtaErrorMessage, ctaErrorMessage);
}}
daledah marked this conversation as resolved.
Show resolved Hide resolved
ctaErrorMessage={ctaErrorMessage}
illustration={LottieAnimations.TripsEmptyState}
Expand Down
7 changes: 1 addition & 6 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5132,16 +5132,11 @@ const styles = (theme: ThemeColors) =>
height: '100%',
},

emptyStateScrollView: {
height: '100%',
flex: 1,
},

emptyStateForeground: {
margin: 32,
justifyContent: 'center',
alignItems: 'center',
flex: 1,
flexGrow: 1,
},

emptyStateContent: {
Expand Down
Loading