diff --git a/assets/images/folder.svg b/assets/images/folder.svg
new file mode 100644
index 000000000000..17cef959132f
--- /dev/null
+++ b/assets/images/folder.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/product-illustrations/emptystate__expenses.svg b/assets/images/product-illustrations/emptystate__expenses.svg
new file mode 100644
index 000000000000..c01a89109cbf
--- /dev/null
+++ b/assets/images/product-illustrations/emptystate__expenses.svg
@@ -0,0 +1,58 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__folder-open.svg b/assets/images/simple-illustrations/simple-illustration__folder-open.svg
new file mode 100644
index 000000000000..c104313a9b6c
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__folder-open.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index fc1e531b1f1d..080d8cdd5655 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -507,6 +507,10 @@ const ROUTES = {
route: 'workspace/:policyID/members',
getRoute: (policyID: string) => `workspace/${policyID}/members` as const,
},
+ WORKSPACE_CATEGORIES: {
+ route: 'workspace/:policyID/categories',
+ getRoute: (policyID: string) => `workspace/${policyID}/categories` as const,
+ },
// Referral program promotion
REFERRAL_DETAILS_MODAL: {
route: 'referral/:contentType',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index da7ea8db5ee6..50ced3ff256a 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -207,6 +207,7 @@ const SCREENS = {
MEMBERS: 'Workspace_Members',
INVITE: 'Workspace_Invite',
INVITE_MESSAGE: 'Workspace_Invite_Message',
+ CATEGORIES: 'Workspace_Categories',
CURRENCY: 'Workspace_Profile_Currency',
DESCRIPTION: 'Workspace_Profile_Description',
SHARE: 'Workspace_Profile_Share',
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index 553a60e568ec..6a2fb1c6b1f6 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -68,6 +68,7 @@ import Flag from '@assets/images/flag.svg';
import FlagLevelOne from '@assets/images/flag_level_01.svg';
import FlagLevelTwo from '@assets/images/flag_level_02.svg';
import FlagLevelThree from '@assets/images/flag_level_03.svg';
+import Folder from '@assets/images/folder.svg';
import Fullscreen from '@assets/images/fullscreen.svg';
import Gallery from '@assets/images/gallery.svg';
import Gear from '@assets/images/gear.svg';
@@ -216,6 +217,7 @@ export {
FlagLevelTwo,
FlagLevelThree,
Fullscreen,
+ Folder,
Gallery,
Gear,
Globe,
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 3f6b6ca20540..299b694df3f2 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -5,6 +5,7 @@ import BankUserGreen from '@assets/images/product-illustrations/bank-user--green
import ConciergeBlue from '@assets/images/product-illustrations/concierge--blue.svg';
import ConciergeExclamation from '@assets/images/product-illustrations/concierge--exclamation.svg';
import CreditCardsBlue from '@assets/images/product-illustrations/credit-cards--blue.svg';
+import EmptyStateExpenses from '@assets/images/product-illustrations/emptystate__expenses.svg';
import GpsTrackOrange from '@assets/images/product-illustrations/gps-track--orange.svg';
import Hands from '@assets/images/product-illustrations/home-illustration-hands.svg';
import InvoiceOrange from '@assets/images/product-illustrations/invoice--orange.svg';
@@ -36,6 +37,7 @@ import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustra
import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg';
import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg';
import EmailAddress from '@assets/images/simple-illustrations/simple-illustration__email-address.svg';
+import FolderOpen from '@assets/images/simple-illustrations/simple-illustration__folder-open.svg';
import Gears from '@assets/images/simple-illustrations/simple-illustration__gears.svg';
import HandCard from '@assets/images/simple-illustrations/simple-illustration__handcard.svg';
import HandEarth from '@assets/images/simple-illustrations/simple-illustration__handearth.svg';
@@ -76,6 +78,8 @@ export {
ConciergeExclamation,
CreditCardsBlue,
EmailAddress,
+ EmptyStateExpenses,
+ FolderOpen,
HandCard,
HotDogStand,
InvoiceOrange,
diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx
index eb9450f6ad98..2f853dc55839 100644
--- a/src/components/SelectionList/BaseListItem.tsx
+++ b/src/components/SelectionList/BaseListItem.tsx
@@ -67,7 +67,7 @@ function BaseListItem({
{canSelectMultiple && (
diff --git a/src/components/SelectionList/RadioListItem.tsx b/src/components/SelectionList/RadioListItem.tsx
index c8a0d5d5d593..b3340a0faf7a 100644
--- a/src/components/SelectionList/RadioListItem.tsx
+++ b/src/components/SelectionList/RadioListItem.tsx
@@ -35,27 +35,30 @@ function RadioListItem({
rightHandSideComponent={rightHandSideComponent}
keyForList={item.keyForList}
>
-
-
-
- {!!item.alternateText && (
+ <>
+
- )}
-
+
+ {!!item.alternateText && (
+
+ )}
+
+ {!!item.rightElement && item.rightElement}
+ >
);
}
diff --git a/src/components/WorkspaceEmptyStateSection.tsx b/src/components/WorkspaceEmptyStateSection.tsx
new file mode 100644
index 000000000000..330f8e1ebbf5
--- /dev/null
+++ b/src/components/WorkspaceEmptyStateSection.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import {View} from 'react-native';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import type IconAsset from '@src/types/utils/IconAsset';
+import Icon from './Icon';
+import Text from './Text';
+
+type WorkspaceEmptyStateSectionProps = {
+ /** The text to display in the title of the section */
+ title: string;
+
+ /** The text to display in the subtitle of the section */
+ subtitle?: string;
+
+ /** The icon to display along with the title */
+ icon: IconAsset;
+};
+
+function WorkspaceEmptyStateSection({icon, subtitle, title}: WorkspaceEmptyStateSectionProps) {
+ const styles = useThemeStyles();
+ const {isSmallScreenWidth} = useWindowDimensions();
+
+ return (
+ <>
+
+
+
+
+
+ {title}
+
+
+ {!!subtitle && (
+
+ {subtitle}
+
+ )}
+
+
+ >
+ );
+}
+WorkspaceEmptyStateSection.displayName = 'WorkspaceEmptyStateSection';
+
+export default WorkspaceEmptyStateSection;
diff --git a/src/languages/en.ts b/src/languages/en.ts
index b6a24f33035c..ffb764b40e6a 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -121,6 +121,7 @@ export default {
no: 'No',
ok: 'OK',
buttonConfirm: 'Got it',
+ name: 'Name',
attachment: 'Attachment',
to: 'To',
optional: 'Optional',
@@ -1684,9 +1685,12 @@ export default {
card: 'Cards',
workspace: 'Workspace',
edit: 'Edit workspace',
+ enabled: 'Enabled',
+ disabled: 'Disabled',
delete: 'Delete workspace',
settings: 'Settings',
reimburse: 'Reimbursements',
+ categories: 'Categories',
bills: 'Bills',
invoices: 'Invoices',
travel: 'Travel',
@@ -1715,6 +1719,13 @@ export default {
control: 'Control',
collect: 'Collect',
},
+ categories: {
+ subtitle: 'Get a better overview of where money is being spent. Use our default categories or add your own.',
+ emptyCategories: {
+ title: "You haven't created any categories",
+ subtitle: 'Add a category to organize your spend.',
+ },
+ },
emptyWorkspace: {
title: 'Create a workspace',
subtitle: 'Workspaces are where you’ll chat with your team, reimburse expenses, issue cards, send invoices, pay bills, and more - all in one place.',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index fc6755519d6f..b03cbdd3772b 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -111,6 +111,7 @@ export default {
no: 'No',
ok: 'OK',
buttonConfirm: 'Ok, entendido',
+ name: 'Nombre',
attachment: 'Archivo adjunto',
to: 'A',
optional: 'Opcional',
@@ -1708,9 +1709,12 @@ export default {
card: 'Tarjetas',
workspace: 'Espacio de trabajo',
edit: 'Editar espacio de trabajo',
+ enabled: 'Activada',
+ disabled: 'Desactivada',
delete: 'Eliminar espacio de trabajo',
settings: 'Configuración',
reimburse: 'Reembolsos',
+ categories: 'Categorías',
bills: 'Pagar facturas',
invoices: 'Enviar facturas',
travel: 'Viajes',
@@ -1739,6 +1743,13 @@ export default {
control: 'Control',
collect: 'Recolectar',
},
+ categories: {
+ subtitle: 'Obtén una visión general de dónde te gastas el dinero. Utiliza las categorías predeterminadas o añade las tuyas propias.',
+ emptyCategories: {
+ title: 'No has creado ninguna categoría',
+ subtitle: 'Añade una categoría para organizar tu gasto.',
+ },
+ },
emptyWorkspace: {
title: 'Crea un espacio de trabajo',
subtitle: 'En los espacios de trabajo podrás chatear con tu equipo, reembolsar gastos, emitir tarjetas, enviar y pagar facturas, y mucho más - todo en un mismo lugar.',
diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
index 14aa6de27116..5e14ad9fca29 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
@@ -22,6 +22,7 @@ const workspaceSettingsScreens = {
[SCREENS.WORKSPACE.INVOICES]: () => require('../../../../../pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType,
[SCREENS.WORKSPACE.TRAVEL]: () => require('../../../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType,
[SCREENS.WORKSPACE.MEMBERS]: () => require('../../../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType,
} satisfies Screens;
function BaseCentralPaneNavigator() {
diff --git a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
index 446fb479ea09..47b646f4d150 100755
--- a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
@@ -12,6 +12,7 @@ const TAB_TO_CENTRAL_PANE_MAPPING: Record = {
SCREENS.WORKSPACE.INVOICES,
SCREENS.WORKSPACE.TRAVEL,
SCREENS.WORKSPACE.MEMBERS,
+ SCREENS.WORKSPACE.CATEGORIES,
],
};
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index ad3dc305f619..9428379430dd 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -61,6 +61,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.MEMBERS]: {
path: ROUTES.WORKSPACE_MEMBERS.route,
},
+ [SCREENS.WORKSPACE.CATEGORIES]: {
+ path: ROUTES.WORKSPACE_CATEGORIES.route,
+ },
},
},
[SCREENS.NOT_FOUND]: '*',
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 870e7dd7f2ad..1aae7dae1a7f 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -74,6 +74,9 @@ type CentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.MEMBERS]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.CATEGORIES]: {
+ policyID: string;
+ };
};
type WorkspaceSwitcherNavigatorParamList = {
diff --git a/src/pages/workspace/AdminPolicyAccessOrNotFoundWrapper.tsx b/src/pages/workspace/AdminPolicyAccessOrNotFoundWrapper.tsx
new file mode 100644
index 000000000000..fe5597827ae4
--- /dev/null
+++ b/src/pages/workspace/AdminPolicyAccessOrNotFoundWrapper.tsx
@@ -0,0 +1,66 @@
+/* eslint-disable rulesdir/no-negated-variables */
+import React, {useEffect} from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
+import {withOnyx} from 'react-native-onyx';
+import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
+import Navigation from '@libs/Navigation/Navigation';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
+import * as Policy from '@userActions/Policy';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type * as OnyxTypes from '@src/types/onyx';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
+
+type AdminAccessOrNotFoundOnyxProps = {
+ /** The report currently being looked at */
+ policy: OnyxEntry;
+
+ /** Indicated whether the report data is loading */
+ isLoadingReportData: OnyxEntry;
+};
+
+type AdminPolicyAccessOrNotFoundComponentProps = AdminAccessOrNotFoundOnyxProps & {
+ /** The children to render */
+ children: ((props: AdminAccessOrNotFoundOnyxProps) => React.ReactNode) | React.ReactNode;
+
+ /** The report currently being looked at */
+ policyID: string;
+};
+
+function AdminPolicyAccessOrNotFoundComponent(props: AdminPolicyAccessOrNotFoundComponentProps) {
+ const isPolicyIDInRoute = !!props.policyID?.length;
+
+ useEffect(() => {
+ if (!isPolicyIDInRoute || !isEmptyObject(props.policy)) {
+ // If the workspace is not required or is already loaded, we don't need to call the API
+ return;
+ }
+
+ Policy.openWorkspace(props.policyID, []);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isPolicyIDInRoute, props.policyID]);
+
+ const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.policy ?? {}).length || !props.policy?.id);
+
+ const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !PolicyUtils.isPolicyAdmin(props.policy);
+
+ if (shouldShowFullScreenLoadingIndicator) {
+ return ;
+ }
+
+ if (shouldShowNotFoundPage) {
+ return Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(props.policyID))} />;
+ }
+
+ return <>{typeof props.children === 'function' ? props.children(props) : props.children}>;
+}
+
+export default withOnyx({
+ policy: {
+ key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID ?? ''}`,
+ },
+ isLoadingReportData: {
+ key: ONYXKEYS.IS_LOADING_REPORT_DATA,
+ },
+})(AdminPolicyAccessOrNotFoundComponent);
diff --git a/src/pages/workspace/PaidPolicyAccessOrNotFoundWrapper.tsx b/src/pages/workspace/PaidPolicyAccessOrNotFoundWrapper.tsx
new file mode 100644
index 000000000000..eafd42c6e996
--- /dev/null
+++ b/src/pages/workspace/PaidPolicyAccessOrNotFoundWrapper.tsx
@@ -0,0 +1,66 @@
+/* eslint-disable rulesdir/no-negated-variables */
+import React, {useEffect} from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
+import {withOnyx} from 'react-native-onyx';
+import FullscreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
+import Navigation from '@libs/Navigation/Navigation';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
+import * as Policy from '@userActions/Policy';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type * as OnyxTypes from '@src/types/onyx';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
+
+type PaidPolicyAccessOrNotFoundOnyxProps = {
+ /** The report currently being looked at */
+ policy: OnyxEntry;
+
+ /** Indicated whether the report data is loading */
+ isLoadingReportData: OnyxEntry;
+};
+
+type PaidPolicyAccessOrNotFoundComponentProps = PaidPolicyAccessOrNotFoundOnyxProps & {
+ /** The children to render */
+ children: ((props: PaidPolicyAccessOrNotFoundOnyxProps) => React.ReactNode) | React.ReactNode;
+
+ /** The report currently being looked at */
+ policyID: string;
+};
+
+function PaidPolicyAccessOrNotFoundComponent(props: PaidPolicyAccessOrNotFoundComponentProps) {
+ const isPolicyIDInRoute = !!props.policyID?.length;
+
+ useEffect(() => {
+ if (!isPolicyIDInRoute || !isEmptyObject(props.policy)) {
+ // If the workspace is not required or is already loaded, we don't need to call the API
+ return;
+ }
+
+ Policy.openWorkspace(props.policyID, []);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isPolicyIDInRoute, props.policyID]);
+
+ const shouldShowFullScreenLoadingIndicator = props.isLoadingReportData !== false && (!Object.entries(props.policy ?? {}).length || !props.policy?.id);
+
+ const shouldShowNotFoundPage = isEmptyObject(props.policy) || !props.policy?.id || !PolicyUtils.isPaidGroupPolicy(props.policy) || !props.policy.isPolicyExpenseChatEnabled;
+
+ if (shouldShowFullScreenLoadingIndicator) {
+ return ;
+ }
+
+ if (shouldShowNotFoundPage) {
+ return Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(props.policyID))} />;
+ }
+
+ return <>{typeof props.children === 'function' ? props.children(props) : props.children}>;
+}
+
+export default withOnyx({
+ policy: {
+ key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID ?? ''}`,
+ },
+ isLoadingReportData: {
+ key: ONYXKEYS.IS_LOADING_REPORT_DATA,
+ },
+})(PaidPolicyAccessOrNotFoundComponent);
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index 70d871849ee6..3f0cf65187ee 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -158,6 +158,12 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r
brickRoadIndicator: hasMembersError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
routeName: SCREENS.WORKSPACE.MEMBERS,
},
+ {
+ translationKey: 'workspace.common.categories',
+ icon: Expensicons.Folder,
+ action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES.getRoute(policyID)))),
+ routeName: SCREENS.WORKSPACE.CATEGORIES,
+ },
];
const menuItems: WorkspaceMenuItem[] = [
diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
new file mode 100644
index 000000000000..b8a65c28806b
--- /dev/null
+++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
@@ -0,0 +1,136 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useMemo, useState} from 'react';
+import {View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import TableListItem from '@components/SelectionList/TableListItem';
+import Text from '@components/Text';
+import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection';
+import useLocalize from '@hooks/useLocalize';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import type {CentralPaneNavigatorParamList} from '@navigation/types';
+import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
+import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type SCREENS from '@src/SCREENS';
+import type * as OnyxTypes from '@src/types/onyx';
+
+type PolicyForList = {
+ value: string;
+ text: string;
+ keyForList: string;
+ isSelected: boolean;
+ rightElement: React.ReactNode;
+};
+
+type WorkspaceCategoriesOnyxProps = {
+ /** Collection of categories attached to a policy */
+ policyCategories: OnyxEntry;
+};
+
+type WorkspaceCategoriesPageProps = WorkspaceCategoriesOnyxProps & StackScreenProps;
+
+function WorkspaceCategoriesPage({policyCategories, route}: WorkspaceCategoriesPageProps) {
+ const {isSmallScreenWidth} = useWindowDimensions();
+ const styles = useThemeStyles();
+ const theme = useTheme();
+ const {translate} = useLocalize();
+ const [selectedCategories, setSelectedCategories] = useState>({});
+
+ const categoryList = useMemo(
+ () =>
+ Object.values(policyCategories ?? {}).map((value) => ({
+ value: value.name,
+ text: value.name,
+ keyForList: value.name,
+ isSelected: !!selectedCategories[value.name],
+ rightElement: (
+
+ {value.enabled ? translate('workspace.common.enabled') : translate('workspace.common.disabled')}
+
+
+
+
+ ),
+ })),
+ [policyCategories, selectedCategories, styles.alignSelfCenter, styles.disabledText, styles.flexRow, styles.p1, styles.pl2, theme.icon, translate],
+ );
+
+ const toggleCategory = (category: PolicyForList) => {
+ setSelectedCategories((prev) => ({
+ ...prev,
+ [category.value]: !prev[category.value],
+ }));
+ };
+
+ const toggleAllCategories = () => {
+ const isAllSelected = categoryList.every((category) => !!selectedCategories[category.value]);
+ setSelectedCategories(isAllSelected ? {} : Object.fromEntries(categoryList.map((item) => [item.value, true])));
+ };
+
+ const getCustomListHeader = () => (
+
+ {translate('common.name')}
+ {translate('statusPage.status')}
+
+ );
+
+ return (
+
+
+
+
+
+ {translate('workspace.categories.subtitle')}
+
+ {categoryList.length ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
+
+WorkspaceCategoriesPage.displayName = 'WorkspaceCategoriesPage';
+
+export default withOnyx({
+ policyCategories: {
+ key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${route.params.policyID}`,
+ },
+})(WorkspaceCategoriesPage);
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 238ba1afc781..62d50df5ef5e 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -3439,6 +3439,19 @@ const styles = (theme: ThemeColors) =>
alignItems: 'center',
},
+ emptyCardSectionTitle: {
+ fontSize: variables.fontSizeXLarge,
+ lineHeight: variables.lineHeightXXLarge,
+ textAlign: 'center',
+ },
+
+ emptyCardSectionSubtitle: {
+ fontSize: variables.fontSizeNormal,
+ lineHeight: variables.lineHeightXLarge,
+ color: theme.textSupporting,
+ textAlign: 'center',
+ },
+
transferBalance: {
width: 'auto',
borderRadius: 0,
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 833907549133..3ac9a7f8e718 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -17,6 +17,7 @@ import type {ThemeStyles} from '..';
import shouldPreventScrollOnAutoCompleteSuggestion from './autoCompleteSuggestion';
import getCardStyles from './cardStyles';
import containerComposeStyles from './containerComposeStyles';
+import cursor from './cursor';
import FontUtils from './FontUtils';
import createModalStyleUtils from './generators/ModalStyleUtils';
import createReportActionContextMenuStyleUtils from './generators/ReportActionContextMenuStyleUtils';
@@ -928,6 +929,7 @@ function getCheckboxPressableStyle(borderRadius = 6): ViewStyle {
alignItems: 'center',
// eslint-disable-next-line object-shorthand
borderRadius: borderRadius,
+ ...cursor.cursorPointer,
};
}
diff --git a/src/styles/utils/spacing.ts b/src/styles/utils/spacing.ts
index b4a6296507a4..f27b07f4eeee 100644
--- a/src/styles/utils/spacing.ts
+++ b/src/styles/utils/spacing.ts
@@ -381,6 +381,10 @@ export default {
paddingVertical: 40,
},
+ pv12: {
+ paddingVertical: 48,
+ },
+
ph0: {
paddingHorizontal: 0,
},