diff --git a/src/components/WidgetContainer.tsx b/src/components/WidgetContainer.tsx
new file mode 100644
index 000000000000..097428946680
--- /dev/null
+++ b/src/components/WidgetContainer.tsx
@@ -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. */
+ 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 (
+
+
+ {!!icon && (
+
+
+
+ )}
+
+ {!!title && (
+ {title}
+ )}
+
+
+ {children}
+
+ );
+}
+
+export type {WidgetContainerProps};
+export default WidgetContainer;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 4a583cc1c0c0..60167fe59782 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -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',
+ },
allSettingsScreen: {
subscription: 'Subscription',
domains: 'Domains',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 01c42783ffc0..bc11085ca943 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -733,6 +733,11 @@ const translations: TranslationDeepObject = {
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',
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index 1afe91bbd0a2..d2be5fccf9a0 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -86,7 +86,7 @@ const loadLogOutPreviousUserPage = () => require('../../..
const loadConciergePage = () => require('../../../pages/ConciergePage').default;
const loadTrackExpensePage = () => require('../../../pages/TrackExpensePage').default;
const loadSubmitExpensePage = () => require('../../../pages/SubmitExpensePage').default;
-const loadHomePage = () => require('../../../pages/HomePage').default;
+const loadHomePage = () => require('../../../pages/home/HomePage').default;
const loadWorkspaceJoinUser = () => require('@pages/workspace/WorkspaceJoinUserPage').default;
const loadReportSplitNavigator = () => require('./Navigators/ReportsSplitNavigator').default;
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
deleted file mode 100644
index b388ea37bdc6..000000000000
--- a/src/pages/HomePage.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-import NavigationTabBar from '@components/Navigation/NavigationTabBar';
-import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS';
-import TopBar from '@components/Navigation/TopBar';
-import ScreenWrapper from '@components/ScreenWrapper';
-import useLocalize from '@hooks/useLocalize';
-import useResponsiveLayout from '@hooks/useResponsiveLayout';
-
-function HomePage() {
- const {shouldUseNarrowLayout} = useResponsiveLayout();
- const shouldDisplayLHB = !shouldUseNarrowLayout;
- const {translate} = useLocalize();
-
- return (
-
- )
- }
- >
-
- {shouldDisplayLHB && }
-
- );
-}
-
-export default HomePage;
diff --git a/src/pages/home/AnnouncementsSection.tsx b/src/pages/home/AnnouncementsSection.tsx
new file mode 100644
index 000000000000..60e68ef73933
--- /dev/null
+++ b/src/pages/home/AnnouncementsSection.tsx
@@ -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 (
+
+
+
+ );
+}
+
+export default AnnouncementsSection;
diff --git a/src/pages/home/DiscoverSection.tsx b/src/pages/home/DiscoverSection.tsx
new file mode 100644
index 000000000000..750b8b1772ea
--- /dev/null
+++ b/src/pages/home/DiscoverSection.tsx
@@ -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 (
+
+
+
+ );
+}
+
+export default DiscoverSection;
diff --git a/src/pages/home/ForYouSection.tsx b/src/pages/home/ForYouSection.tsx
new file mode 100644
index 000000000000..404f9c759d40
--- /dev/null
+++ b/src/pages/home/ForYouSection.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ForYouSection;
diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx
new file mode 100644
index 000000000000..e6d79169c224
--- /dev/null
+++ b/src/pages/home/HomePage.tsx
@@ -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 (
+
+ )
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ {shouldDisplayLHB && }
+
+ );
+}
+
+export default HomePage;
diff --git a/src/styles/index.ts b/src/styles/index.ts
index fbd184e9bfe1..30ed85093ac3 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -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,
@@ -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) =>
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 4c509891322d..ae9ebc8f305f 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -104,6 +104,8 @@ export default {
sideBarWidth: 375,
sidePanelWidth: 375,
receiptPaneRHPMaxWidth: 465,
+ homePageLeftColumnMaxWidth: 920,
+ homePageRightColumnMaxWidth: 460,
superWideRHPMaxWidth: 1260,
minScanTooltipWidth: 320,
uploadViewMargin: 20,