Skip to content
Merged
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
133 changes: 85 additions & 48 deletions src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,31 @@ import useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyledSafeAreaInsets from '@hooks/useStyledSafeAreaInsets';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CardUtils from '@libs/CardUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import {getCompanyFeeds} from '@libs/CardUtils';
import {getLatestErrorField} from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {FullScreenNavigatorParamList} from '@libs/Navigation/types';
import {getPerDiemCustomUnit, isControlPolicy} from '@libs/PolicyUtils';
import * as Category from '@userActions/Policy/Category';
import * as DistanceRate from '@userActions/Policy/DistanceRate';
import * as PerDiem from '@userActions/Policy/PerDiem';
import * as Policy from '@userActions/Policy/Policy';
import * as Tag from '@userActions/Policy/Tag';
import * as Report from '@userActions/Report';
import {enablePolicyCategories} from '@userActions/Policy/Category';
import {enablePolicyDistanceRates} from '@userActions/Policy/DistanceRate';
import {enablePerDiem} from '@userActions/Policy/PerDiem';
import {
clearPolicyErrorField,
enableCompanyCards,
enableExpensifyCard,
enablePolicyConnections,
enablePolicyInvoicing,
enablePolicyReportFields,
enablePolicyRules,
enablePolicyTaxes,
enablePolicyWorkflows,
openPolicyMoreFeaturesPage,
} from '@userActions/Policy/Policy';
import {enablePolicyTags} from '@userActions/Policy/Tag';
import {navigateToConciergeChat} from '@userActions/Report';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -63,6 +75,7 @@ type SectionObject = {

function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPageProps) {
const styles = useThemeStyles();
const stylesutils = useStyleUtils();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {safeAreaPaddingBottomStyle} = useStyledSafeAreaInsets();
const {translate} = useLocalize();
Expand Down Expand Up @@ -103,7 +116,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
if (!policyID) {
return;
}
DistanceRate.enablePolicyDistanceRates(policyID, isEnabled);
enablePolicyDistanceRates(policyID, isEnabled);
},
},
{
Expand All @@ -117,7 +130,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
if (!policyID) {
return;
}
Policy.enableExpensifyCard(policyID, isEnabled);
enableExpensifyCard(policyID, isEnabled);
},
disabledAction: () => {
setIsDisableExpensifyCardWarningModalOpen(true);
Expand All @@ -131,7 +144,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
subtitleTranslationKey: 'workspace.moreFeatures.companyCards.subtitle',
isActive: policy?.areCompanyCardsEnabled ?? false,
pendingAction: policy?.pendingFields?.areCompanyCardsEnabled,
disabled: !isEmptyObject(CardUtils.getCompanyFeeds(cardFeeds)),
disabled: !isEmptyObject(getCompanyFeeds(cardFeeds)),
action: (isEnabled: boolean) => {
if (!policyID) {
return;
Expand All @@ -140,7 +153,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.companyCards.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)));
return;
}
Policy.enableCompanyCards(policyID, isEnabled);
enableCompanyCards(policyID, isEnabled);
},
disabledAction: () => {
setIsDisableCompanyCardsWarningModalOpen(true);
Expand All @@ -162,7 +175,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.perDiem.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)));
return;
}
PerDiem.enablePerDiem(policyID, isEnabled, perDiemCustomUnit?.customUnitID);
enablePerDiem(policyID, isEnabled, perDiemCustomUnit?.customUnitID);
},
});
}
Expand All @@ -178,7 +191,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
if (!policyID) {
return;
}
Policy.enablePolicyWorkflows(policyID, isEnabled);
enablePolicyWorkflows(policyID, isEnabled);
},
},
{
Expand All @@ -196,7 +209,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.rules.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)));
return;
}
Policy.enablePolicyRules(policyID, isEnabled);
enablePolicyRules(policyID, isEnabled);
},
},
];
Expand All @@ -212,7 +225,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
if (!policyID) {
return;
}
Policy.enablePolicyInvoicing(policyID, isEnabled);
enablePolicyInvoicing(policyID, isEnabled);
},
},
];
Expand All @@ -230,7 +243,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
if (!policyID) {
return;
}
Category.enablePolicyCategories(policyID, isEnabled);
enablePolicyCategories(policyID, isEnabled);
},
},
{
Expand All @@ -245,7 +258,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
if (!policyID) {
return;
}
Tag.enablePolicyTags(policyID, isEnabled);
enablePolicyTags(policyID, isEnabled);
},
},
{
Expand All @@ -260,7 +273,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
if (!policyID) {
return;
}
Policy.enablePolicyTaxes(policyID, isEnabled);
enablePolicyTaxes(policyID, isEnabled);
},
},
{
Expand All @@ -283,7 +296,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
return;
}

Policy.enablePolicyReportFields(policyID, true);
enablePolicyReportFields(policyID, true);
return;
}
setIsReportFieldsWarningModalOpen(true);
Expand All @@ -308,15 +321,15 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
if (!policyID) {
return;
}
Policy.enablePolicyConnections(policyID, isEnabled);
enablePolicyConnections(policyID, isEnabled);
},
disabled: hasAccountingConnection,
errors: ErrorUtils.getLatestErrorField(policy ?? {}, CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED),
errors: getLatestErrorField(policy ?? {}, CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED),
onCloseError: () => {
if (!policyID) {
return;
}
Policy.clearPolicyErrorField(policyID, CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED);
clearPolicyErrorField(policyID, CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED);
},
},
];
Expand All @@ -327,6 +340,16 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
subtitleTranslationKey: 'workspace.moreFeatures.spendSection.subtitle',
items: spendItems,
},
{
titleTranslationKey: 'workspace.moreFeatures.integrateSection.title',
subtitleTranslationKey: 'workspace.moreFeatures.integrateSection.subtitle',
items: integrateItems,
},
{
titleTranslationKey: 'workspace.moreFeatures.organizeSection.title',
subtitleTranslationKey: 'workspace.moreFeatures.organizeSection.subtitle',
items: organizeItems,
},
{
titleTranslationKey: 'workspace.moreFeatures.manageSection.title',
subtitleTranslationKey: 'workspace.moreFeatures.manageSection.subtitle',
Expand All @@ -337,23 +360,13 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
subtitleTranslationKey: 'workspace.moreFeatures.earnSection.subtitle',
items: earnItems,
},
{
titleTranslationKey: 'workspace.moreFeatures.organizeSection.title',
subtitleTranslationKey: 'workspace.moreFeatures.organizeSection.subtitle',
items: organizeItems,
},
{
titleTranslationKey: 'workspace.moreFeatures.integrateSection.title',
subtitleTranslationKey: 'workspace.moreFeatures.integrateSection.subtitle',
items: integrateItems,
},
];

const renderItem = useCallback(
(item: Item) => (
<View
key={item.titleTranslationKey}
style={styles.mt7}
style={[styles.workspaceSectionMoreFeaturesItem, shouldUseNarrowLayout && styles.flexBasis100, shouldUseNarrowLayout && stylesutils.getMinimumWidth(0)]}
>
<ToggleSettingOptionRow
icon={item.icon}
Expand All @@ -372,31 +385,57 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
/>
</View>
),
[styles, translate],
[styles, stylesutils, shouldUseNarrowLayout, translate],
);

/** Used to fill row space in the Section items when there are odd number of items to create equal margins for last odd item. */
const sectionRowFillerItem = useCallback(
(section: SectionObject) => {
if (section.items.length % 2 === 0) {
return null;
}

return (
<View
key="section-filler-col"
aria-hidden
accessibilityElementsHidden
style={[
styles.workspaceSectionMoreFeaturesItem,
shouldUseNarrowLayout && stylesutils.getMinimumWidth(0),
styles.p0,
styles.mt0,
styles.visibilityHidden,
styles.bgTransparent,
]}
/>
);
},
[styles, stylesutils, shouldUseNarrowLayout],
);

const renderSection = useCallback(
(section: SectionObject) => (
<View
key={section.titleTranslationKey}
style={[styles.mt3, shouldUseNarrowLayout ? styles.workspaceSectionMobile : styles.workspaceSection]}
style={[styles.mt3, shouldUseNarrowLayout ? styles.workspaceSectionMobile : {}]}
>
<Section
containerStyles={shouldUseNarrowLayout ? styles.p5 : styles.p8}
title={translate(section.titleTranslationKey)}
titleStyles={styles.textStrong}
subtitle={translate(section.subtitleTranslationKey)}
containerStyles={[styles.ph1, styles.pv0, styles.bgTransparent, styles.noBorderRadius]}
childrenStyles={[styles.flexRow, styles.flexWrap, styles.columnGap3]}
renderTitle={() => <Text style={styles.mutedNormalTextLabel}>{translate(section.titleTranslationKey)}</Text>}
subtitleMuted
>
{section.items.map(renderItem)}
{sectionRowFillerItem(section)}
</Section>
</View>
),
[shouldUseNarrowLayout, styles, renderItem, translate],
[shouldUseNarrowLayout, styles, renderItem, translate, sectionRowFillerItem],
);

const fetchFeatures = useCallback(() => {
Policy.openPolicyMoreFeaturesPage(route.params.policyID);
openPolicyMoreFeaturesPage(route.params.policyID);
}, [route.params.policyID]);

useNetwork({onReconnect: fetchFeatures});
Expand Down Expand Up @@ -426,9 +465,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
/>

<ScrollView contentContainerStyle={safeAreaPaddingBottomStyle}>
<Text style={[styles.ph5, styles.mb4, styles.mt3, styles.textSupporting, shouldUseNarrowLayout ? styles.workspaceSectionMobile : styles.workspaceSection]}>
{translate('workspace.moreFeatures.subtitle')}
</Text>
<Text style={[styles.ph5, styles.mb5, styles.mt3, styles.textSupporting, styles.workspaceSectionMobile]}>{translate('workspace.moreFeatures.subtitle')}</Text>
{sections.map(renderSection)}
</ScrollView>

Expand Down Expand Up @@ -470,7 +507,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
return;
}
setIsReportFieldsWarningModalOpen(false);
Policy.enablePolicyReportFields(policyID, false);
enablePolicyReportFields(policyID, false);
}}
onCancel={() => setIsReportFieldsWarningModalOpen(false)}
prompt={translate('workspace.reportFields.disableReportFieldsConfirmation')}
Expand All @@ -483,7 +520,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
isVisible={isDisableExpensifyCardWarningModalOpen}
onConfirm={() => {
setIsDisableExpensifyCardWarningModalOpen(false);
Report.navigateToConciergeChat(true);
navigateToConciergeChat(true);
}}
onCancel={() => setIsDisableExpensifyCardWarningModalOpen(false)}
prompt={translate('workspace.moreFeatures.expensifyCard.disableCardPrompt')}
Expand All @@ -495,7 +532,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
isVisible={isDisableCompanyCardsWarningModalOpen}
onConfirm={() => {
setIsDisableCompanyCardsWarningModalOpen(false);
Report.navigateToConciergeChat(true);
navigateToConciergeChat(true);
}}
onCancel={() => setIsDisableCompanyCardsWarningModalOpen(false)}
prompt={translate('workspace.moreFeatures.companyCards.disableCardPrompt')}
Expand Down
13 changes: 13 additions & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4918,6 +4918,19 @@ const styles = (theme: ThemeColors) =>
alignSelf: 'center',
},

workspaceSectionMoreFeaturesItem: {
backgroundColor: theme.cardBG,
borderRadius: variables.componentBorderRadiusNormal,
paddingHorizontal: 16,
paddingVertical: 20,
minWidth: 350,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I kept a 350 width minimum width till it be a column layout.

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.

Do we have any breakpoints we already use in the product? Like the same way we would use a media query... if we are at a "tablet" size, maybe that's when we switch to a single column.

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 throwing it out there in case we can kind of align on how the app responds to different widths.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Do we want to force 1 column on tablet portraits where the screen width is less than 800px?

Currently, 350 will allow it to be fit two rows in 760 < screenwidth >= 800.

image

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.

Can you show me what 1 column looks like at 800px screen width? Just to compare. Thanks!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Enabled 1-column on < 800 Screens.

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.

@parasharrajat, I have one doubt here. Passing minWidth: 350 might cause some UI distortion on devices with smaller widths. While testing using Chrome's Inspect tool on the Fold 5 (a virtual device in Chrome Inspect, not an actual one), I noticed a UI issue where the card extends beyond the screen since the width for that device is 344px.
Screenshot 2025-01-22 at 6 50 50 PM

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Let me fix that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@jayeshmangwani Done. Check now.

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.

Checking

flexGrow: 1,
flexShrink: 1,
// Choosing a lowest value just above the threshold for the items to adjust width against the various screens. Only 2 items are shown 35 * 2 = 70 thus third item of 35% width can't fit forcing a two column layout.
flexBasis: '35%',
marginTop: 12,
},

aspectRatioLottie: (animation: DotLottieAnimation) => ({aspectRatio: animation.w / animation.h}),

receiptDropHeaderGap: {
Expand Down
4 changes: 4 additions & 0 deletions src/styles/utils/flex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export default {
flexBasis: 'auto',
},

flexBasis100: {
flexBasis: '100%',
},

flexBasis0: {
flexBasis: 0,
},
Expand Down
4 changes: 4 additions & 0 deletions src/styles/utils/spacing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,10 @@ export default {
rowGap: 16,
},

columnGap3: {
columnGap: 12,
},

minHeight5: {
minHeight: 20,
},
Expand Down