Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
66 changes: 66 additions & 0 deletions src/components/WidgetContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type {ReactNode} from 'react';
import React from 'react';
import {View} from 'react-native';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import type IconAsset from '@src/types/utils/IconAsset';
import Icon from './Icon';
import Text from './Text';

type WidgetContainerProps = {
/** The icon to display along with the title */
icon?: IconAsset;

/** The text to display in the title of the widget */
title?: string;

/** Custom color for the title text */
titleColor?: string;

/** The width of the icon. */
Comment on lines +12 to +21

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking
Maybe we should just pass the icon as a parameter?
Instead of three parameters

iconWidth?: number;

/** The height of the icon. */
iconHeight?: number;

/** The content to display inside the widget container */
children: ReactNode;
};

function WidgetContainer({
children,
icon,
title,
titleColor,
iconWidth = variables.iconSizeNormal,
iconHeight = variables.iconSizeNormal,
}: WidgetContainerProps) {
const styles = useThemeStyles();
const theme = useTheme();

return (
<View style={styles.widgetContainer}>
<View style={[styles.flexRow, styles.alignItemsStart, styles.mh8, styles.mt8, styles.mb5]}>
{!!icon && (
<View style={[styles.flexGrow0, styles.flexShrink0]}>
<Icon
src={icon}
width={iconWidth}
height={iconHeight}
/>
</View>
)}
<View style={[styles.flexShrink1, styles.flexGrow1, styles.flexRow, styles.alignItemsCenter, styles.gap2]}>
{!!title && (
<Text style={styles.getWidgetContainerTitleStyle(titleColor ?? theme.text)}>{title}</Text>
)}
</View>
</View>
{children}
</View>
);
}

export type {WidgetContainerProps};
export default WidgetContainer;
5 changes: 5 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,11 @@ const translations = {
description: "We're fine-tuning a few more bits and pieces of New Expensify to accommodate your specific setup. In the meantime, head over to Expensify Classic.",
},
},
homePage: {
forYou: 'For you',
announcements: 'Announcements',
discover: 'Discover',
},
Comment on lines +991 to +995

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add homePage strings to all locales

Only en and es define the new homePage.* keys, but translate() (see src/libs/Localize/index.ts) throws in dev and returns the raw path / missing translation sentinel in staging/production when a key is missing for the active locale. That means users on any other locale (e.g. fr/de/pt-BR) will see homePage.forYou/homePage.discover instead of readable titles on the Home page. Please add the new keys to all language files (or a proper fallback) to avoid missing-translation behavior.

Useful? React with 👍 / 👎.

allSettingsScreen: {
subscription: 'Subscription',
domains: 'Domains',
Expand Down
5 changes: 5 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,11 @@ const translations: TranslationDeepObject<typeof en> = {
description: 'Estamos ajustando algunos detalles de New Expensify para adaptarla a tu configuración específica. Mientras tanto, dirígete a Expensify Classic.',
},
},
homePage: {
forYou: 'Para ti',
announcements: 'Anuncios',
discover: 'Descubrir',
},
allSettingsScreen: {
subscription: 'Suscripcion',
domains: 'Dominios',
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const loadLogOutPreviousUserPage = () => require<ReactComponentModule>('../../..
const loadConciergePage = () => require<ReactComponentModule>('../../../pages/ConciergePage').default;
const loadTrackExpensePage = () => require<ReactComponentModule>('../../../pages/TrackExpensePage').default;
const loadSubmitExpensePage = () => require<ReactComponentModule>('../../../pages/SubmitExpensePage').default;
const loadHomePage = () => require<ReactComponentModule>('../../../pages/HomePage').default;
const loadHomePage = () => require<ReactComponentModule>('../../../pages/home/HomePage').default;
const loadWorkspaceJoinUser = () => require<ReactComponentModule>('@pages/workspace/WorkspaceJoinUserPage').default;

const loadReportSplitNavigator = () => require<ReactComponentModule>('./Navigators/ReportsSplitNavigator').default;
Expand Down
38 changes: 0 additions & 38 deletions src/pages/HomePage.tsx

This file was deleted.

20 changes: 20 additions & 0 deletions src/pages/home/AnnouncementsSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import {View} from 'react-native';
import WidgetContainer from '@components/WidgetContainer';
import useLocalize from '@hooks/useLocalize';

/**
* This is an empty placeholder component for the Announcements section.
* The actual implementation will be added in upcoming PRs.
*/
function AnnouncementsSection() {
const {translate} = useLocalize();

return (
<WidgetContainer title={translate('homePage.announcements')}>
<View style={{height: 400}} />
</WidgetContainer>
);
}

export default AnnouncementsSection;
20 changes: 20 additions & 0 deletions src/pages/home/DiscoverSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import {View} from 'react-native';
import WidgetContainer from '@components/WidgetContainer';
import useLocalize from '@hooks/useLocalize';

/**
* This is an empty placeholder component for the Discover section.
* The actual implementation will be added in upcoming PRs.
*/
function DiscoverSection() {
const {translate} = useLocalize();

return (
<WidgetContainer title={translate('homePage.discover')}>
<View style={{height: 400}} />
</WidgetContainer>
);
}

export default DiscoverSection;
98 changes: 98 additions & 0 deletions src/pages/home/ForYouSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react';
import {View} from 'react-native';
import Button from '@components/Button';
import WidgetContainer from '@components/WidgetContainer';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import {buildQueryStringFromFilterFormValues} from '@libs/SearchQueryUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {accountIDSelector} from '@src/selectors/Session';

/**
* This is a placeholder component for the For You section.
* The actual implementation will be added in upcoming PRs.
*/
function ForYouSection() {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [accountID] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false, selector: accountIDSelector});

const handleGoToSearch = () => {
Navigation.navigate(
ROUTES.SEARCH_ROOT.getRoute({
query: buildQueryStringFromFilterFormValues({
type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
action: CONST.SEARCH.ACTION_FILTERS.SUBMIT,
from: [`${accountID}`],
}),
}),
);
};

const handleGoToApproveSearch = () => {
Navigation.navigate(
ROUTES.SEARCH_ROOT.getRoute({
query: buildQueryStringFromFilterFormValues({
type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
action: CONST.SEARCH.ACTION_FILTERS.APPROVE,
to: [`${accountID}`],
}),
}),
);
};

const handleGoToPaySearch = () => {
Navigation.navigate(
ROUTES.SEARCH_ROOT.getRoute({
query: buildQueryStringFromFilterFormValues({
type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
action: CONST.SEARCH.ACTION_FILTERS.PAY,
reimbursable: CONST.SEARCH.BOOLEAN.YES,
payer: accountID?.toString(),
}),
}),
);
};

const handleGoToExportSearch = () => {
Navigation.navigate(
ROUTES.SEARCH_ROOT.getRoute({
query: buildQueryStringFromFilterFormValues({
type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT,
action: CONST.SEARCH.ACTION_FILTERS.EXPORT,
exporter: [`${accountID}`],
exportedOn: CONST.SEARCH.DATE_PRESETS.NEVER,
}),
}),
);
};

return (
<WidgetContainer title={translate('homePage.forYou')}>
<View style={[styles.flexColumn, styles.gap3]}>
<Button
text="Go to submitted expense reports"
onPress={handleGoToSearch}
Comment on lines +77 to +79

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Localize For You button labels

These button labels are hard-coded English strings, so users in non-English locales will always see English text even when the app is localized. This is user-facing and inconsistent with the rest of the Home page, which uses translate(). Please add translation keys and use translate() for these labels.

Useful? React with 👍 / 👎.

/>
<Button
text="Go to expense reports to approve"
onPress={handleGoToApproveSearch}
/>
<Button
text="Go to expense reports to pay"
onPress={handleGoToPaySearch}
/>
<Button
text="Go to expense reports to export"
onPress={handleGoToExportSearch}
/>
</View>
</WidgetContainer>
);
}

export default ForYouSection;
68 changes: 68 additions & 0 deletions src/pages/home/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, {useEffect} from 'react';
import {View} from 'react-native';
import NavigationTabBar from '@components/Navigation/NavigationTabBar';
import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {confirmReadyToOpenApp} from '@libs/actions/App';
import usePreloadFullScreenNavigators from '@libs/Navigation/AppNavigator/usePreloadFullScreenNavigators';
import TopBar from '@components/Navigation/TopBar';
import useLocalize from '@hooks/useLocalize';
import AnnouncementsSection from './AnnouncementsSection';
import DiscoverSection from './DiscoverSection';
import ForYouSection from './ForYouSection';

function HomePage() {
const {shouldUseNarrowLayout} = useResponsiveLayout();
const shouldDisplayLHB = !shouldUseNarrowLayout;
const styles = useThemeStyles();
const {translate} = useLocalize();

useEffect(() => {
confirmReadyToOpenApp();
}, []);

// This hook preloads the screens of adjacent tabs to make changing tabs faster.
usePreloadFullScreenNavigators();

return (
<ScreenWrapper
shouldEnablePickerAvoiding={false}
shouldShowOfflineIndicatorInWideScreen
testID="HomePage"
enableEdgeToEdgeBottomSafeAreaPadding={false}
bottomContent={
shouldUseNarrowLayout && (
<NavigationTabBar
selectedTab={NAVIGATION_TABS.HOME}
shouldShowFloatingButtons
/>
)
}
>
<TopBar
breadcrumbLabel={translate('common.home')}
shouldShowLoadingBar={false}
/>
<ScrollView
contentContainerStyle={styles.homePageContentContainer}
addBottomSafeAreaPadding
>
<View style={styles.homePageMainLayout(shouldUseNarrowLayout)}>
<View style={styles.homePageLeftColumn(shouldUseNarrowLayout)}>
<ForYouSection />
<DiscoverSection />
</View>
<View style={styles.homePageRightColumn(shouldUseNarrowLayout)}>
<AnnouncementsSection />
</View>
</View>
</ScrollView>
{shouldDisplayLHB && <NavigationTabBar selectedTab={NAVIGATION_TABS.HOME} />}
</ScreenWrapper>
);
}

export default HomePage;
36 changes: 36 additions & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3663,6 +3663,17 @@ const staticStyles = (theme: ThemeColors) =>
marginHorizontal: variables.sectionMargin,
},

widgetContainer: {
backgroundColor: theme.cardBG,
borderRadius: variables.componentBorderRadiusLarge,
overflow: 'hidden',
},

homePageContentContainer: {
flexGrow: 1,
padding: 20,
},

cardSectionIllustration: {
width: 'auto',
height: variables.sectionIllustrationHeight,
Expand Down Expand Up @@ -6193,6 +6204,31 @@ const plainStyles = (theme: ThemeColors) =>
searchTopBarZIndexStyle: {
zIndex: variables.searchTopBarZIndex,
},

getWidgetContainerTitleStyle: (color: string) =>
({
...FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
fontSize: 17,
lineHeight: 20,
color,
}) satisfies TextStyle,

homePageMainLayout: (shouldUseNarrowLayout: boolean) =>
({
flexDirection: shouldUseNarrowLayout ? 'column' : 'row',
gap: 20,
width: '100%',
}) satisfies ViewStyle,

homePageLeftColumn: (shouldUseNarrowLayout: boolean) =>
shouldUseNarrowLayout
? ({width: '100%', flexDirection: 'column', gap: 20}) satisfies ViewStyle
: ({flex: 2, flexBasis: '66.666%', maxWidth: variables.homePageLeftColumnMaxWidth, flexDirection: 'column', gap: 20}) satisfies ViewStyle,

homePageRightColumn: (shouldUseNarrowLayout: boolean) =>
shouldUseNarrowLayout
? ({width: '100%'}) satisfies ViewStyle
: ({flex: 1, flexBasis: '33.333%', maxWidth: variables.homePageRightColumnMaxWidth}) satisfies ViewStyle,
}) satisfies Styles;

const styles = (theme: ThemeColors) =>
Expand Down
Loading
Loading