Skip to content

Commit

Permalink
Merge pull request #48060 from daledah/fix/47937
Browse files Browse the repository at this point in the history
  • Loading branch information
luacmartins authored Sep 25, 2024
2 parents 1b2395c + cf3b634 commit b1720c7
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 48 deletions.
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
57 changes: 56 additions & 1 deletion src/libs/TripReservationUtils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,43 @@
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;
},
});

let primaryLogin: string;
Onyx.connect({
key: ONYXKEYS.ACCOUNT,
callback: (val) => {
primaryLogin = val?.primaryLogin ?? '';
},
});

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

export {getTripReservationIcon, getReservationsFromTripTransactions, getTripEReceiptIcon};
function bookATrip(translate: LocaleContextProps['translate'], 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};
84 changes: 76 additions & 8 deletions src/pages/Search/EmptySearchView.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,103 @@
import React, {useMemo} from 'react';
import React, {useMemo, useState} from 'react';
import {Linking, View} from 'react-native';
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 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 [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
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, setCtaErrorMessage, ctaErrorMessage),
};
case CONST.SEARCH.DATA_TYPES.CHAT:
case CONST.SEARCH.DATA_TYPES.EXPENSE:
Expand All @@ -46,7 +113,7 @@ function EmptySearchView({type}: EmptySearchViewProps) {
headerContentStyles: styles.emptyStateFolderWebStyles,
};
}
}, [type, StyleUtils, translate, theme, styles.w100, styles.emptyStateFolderWebStyles]);
}, [type, StyleUtils, translate, theme, styles, subtitleComponent, ctaErrorMessage]);

return (
<EmptyStateComponent
Expand All @@ -55,6 +122,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
29 changes: 2 additions & 27 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,9 +32,7 @@ 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);

const [ctaErrorMessage, setCtaErrorMessage] = useState('');
Expand All @@ -46,9 +41,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 +55,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, setCtaErrorMessage, ctaErrorMessage);
}}
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

0 comments on commit b1720c7

Please sign in to comment.