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, },