Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add upgrade screen to more features #45131

Merged
merged 12 commits into from
Jul 17, 2024
41 changes: 35 additions & 6 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5287,24 +5287,53 @@ const CONST = {
},

EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN] as string[],

EMPTY_STATE_MEDIA: {
ANIMATION: 'animation',
ILLUSTRATION: 'illustration',
VIDEO: 'video',
},

UPGRADE_FEATURE_INTRO_MAPPING: [
{
UPGRADE_FEATURE_INTRO_MAPPING: {
reportFields: {
id: 'reportFields',
alias: 'report-fields',
name: 'Report Fields',
title: 'workspace.upgrade.reportFields.title',
description: 'workspace.upgrade.reportFields.description',
icon: 'Pencil',
},
],

netSuite: {
id: 'netSuite',
allroundexperts marked this conversation as resolved.
Show resolved Hide resolved
alias: 'netsuite',
allroundexperts marked this conversation as resolved.
Show resolved Hide resolved
name: 'NetSuite',
allroundexperts marked this conversation as resolved.
Show resolved Hide resolved
title: 'workspace.upgrade.netSuite.title',
description: 'workspace.upgrade.netSuite.description',
icon: 'NetSuiteSquare',
},
sageIntacct: {
id: 'sageIntacct',
alias: 'sage-intacct',
name: 'Sage Intacct',
allroundexperts marked this conversation as resolved.
Show resolved Hide resolved
title: 'workspace.upgrade.sageIntacct.title',
description: 'workspace.upgrade.sageIntacct.description',
icon: 'IntacctSquare',
},
allroundexperts marked this conversation as resolved.
Show resolved Hide resolved
glCodes: {
id: 'glCodes',
alias: 'gl-codes',
name: 'GL codes',
title: 'workspace.upgrade.glCodes.title',
description: 'workspace.upgrade.glCodes.description',
icon: 'Tag',
},
glAndPayrollCodes: {
id: 'glAndPayrollCodes',
alias: 'gl-and-payroll-codes',
name: 'GL & Payroll codes',
title: 'workspace.upgrade.glAndPayrollCodes.title',
description: 'workspace.upgrade.glAndPayrollCodes.description',
icon: 'FolderOpen',
},
},
REPORT_FIELD_TYPES: {
TEXT: 'text',
DATE: 'date',
Expand Down
3 changes: 2 additions & 1 deletion src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -695,7 +695,8 @@ const ROUTES = {
},
WORKSPACE_UPGRADE: {
route: 'settings/workspaces/:policyID/upgrade/:featureName',
getRoute: (policyID: string, featureName: string) => `settings/workspaces/${policyID}/upgrade/${encodeURIComponent(featureName)}` as const,
getRoute: (policyID: string, featureName: string, backTo?: string) =>
getUrlWithBackToParam(`settings/workspaces/${policyID}/upgrade/${encodeURIComponent(featureName)}` as const, backTo),
},
WORKSPACE_CATEGORIES_SETTINGS: {
route: 'settings/workspaces/:policyID/categories/settings',
Expand Down
9 changes: 9 additions & 0 deletions src/components/ConnectToNetSuiteButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import React, {useState} from 'react';
import {useOnyx} from 'react-native-onyx';
import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal';
import Button from '@components/Button';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import {removePolicyConnection} from '@libs/actions/connections';
import Navigation from '@libs/Navigation/Navigation';
import {isControlPolicy} from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {ConnectToNetSuiteButtonProps} from './types';

function ConnectToNetSuiteButton({policyID, shouldDisconnectIntegrationBeforeConnecting, integrationToDisconnect}: ConnectToNetSuiteButtonProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);

const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false);

return (
<>
<Button
onPress={() => {
if (!isControlPolicy(policy)) {
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.netSuite.alias));
return;
}

if (shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect) {
setIsDisconnectModalOpen(true);
return;
Expand Down
10 changes: 10 additions & 0 deletions src/components/ConnectToSageIntacctButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {useRef, useState} from 'react';
import type {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal';
import Button from '@components/Button';
import * as Expensicons from '@components/Icon/Expensicons';
Expand All @@ -11,8 +12,10 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import {removePolicyConnection} from '@libs/actions/connections';
import {getPoliciesConnectedToSageIntacct} from '@libs/actions/Policy/Policy';
import Navigation from '@libs/Navigation/Navigation';
import {isControlPolicy} from '@libs/PolicyUtils';
import type {AnchorPosition} from '@styles/index';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PolicyConnectionName} from '@src/types/onyx/Policy';

Expand All @@ -27,6 +30,8 @@ function ConnectToSageIntacctButton({policyID, shouldDisconnectIntegrationBefore
const {translate} = useLocalize();
const {isOffline} = useNetwork();

const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);

const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false);

const hasPoliciesConnectedToSageIntacct = !!getPoliciesConnectedToSageIntacct().length;
Expand Down Expand Up @@ -57,6 +62,11 @@ function ConnectToSageIntacctButton({policyID, shouldDisconnectIntegrationBefore
<>
<Button
onPress={() => {
if (!isControlPolicy(policy)) {
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.sageIntacct.alias));
return;
}

if (shouldDisconnectIntegrationBeforeConnecting && integrationToDisconnect) {
setIsDisconnectModalOpen(true);
return;
Expand Down
30 changes: 25 additions & 5 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3316,11 +3316,31 @@ export default {
reportFields: {
title: 'Report fields',
description: `Report fields let you specify header-level details, distinct from tags that pertain to expenses on individual line items. These details can encompass specific project names, business trip information, locations, and more.`,
pricing: {
onlyAvailableOnPlan: 'Report fields are only available on the Control plan, starting at ',
amount: '$9 ',
perActiveMember: 'per active member per month.',
},
onlyAvailableOnPlan: 'Report fields are only available on the Control plan, starting at ',
},
netSuite: {
title: 'NetSuite',
description: `Enjoy automated syncing and reduce manual entries with the Expensify + NetSuite integration. Gain in-depth, realtime financial insights with native and custom segment support, including project and customer mapping.`,
allroundexperts marked this conversation as resolved.
Show resolved Hide resolved
onlyAvailableOnPlan: 'Our NetSuite integration is only available on the Control plan, starting at ',
},
sageIntacct: {
title: 'Sage Intacct',
description: `Enjoy automated syncing and reduce manual entries with the Expensify + Sage Intacct integration. Gain in-depth, real-time financial insights with user-defined dimensions, as well as expense coding by department, class, location, customer, and project (job).`,
onlyAvailableOnPlan: 'Our Sage Intacct integration is only available on the Control plan, starting at ',
},
glCodes: {
title: 'GL codes',
description: `Add GL codes to your categories and tags for easy export of expenses to your accounting and payroll systems.`,
onlyAvailableOnPlan: 'GL codes are only available on the Control plan, starting at ',
},
glAndPayrollCodes: {
title: 'GL & Payroll codes',
description: `Add GL & Payroll codes to your categories for easy export of expenses to your accounting and payroll systems.`,
onlyAvailableOnPlan: 'GL & Payroll codes are only available on the Control plan, starting at ',
},
pricing: {
amount: '$9 ',
perActiveMember: 'per active member per month.',
},
note: {
upgradeWorkspace: 'Upgrade your workspace to access this feature, or',
Expand Down
30 changes: 25 additions & 5 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3366,17 +3366,37 @@ export default {
reportFields: {
title: 'Los campos',
description: `Los campos de informe permiten especificar detalles a nivel de cabecera, distintos de las etiquetas que pertenecen a los gastos en partidas individuales. Estos detalles pueden incluir nombres de proyectos específicos, información sobre viajes de negocios, ubicaciones, etc.`,
pricing: {
onlyAvailableOnPlan: 'Los campos de informe sólo están disponibles en el plan Control, a partir de ',
amount: '$9 ',
perActiveMember: 'por miembro activo al mes.',
},
onlyAvailableOnPlan: 'Los campos de informe sólo están disponibles en el plan Control, a partir de ',
},
netSuite: {
title: 'NetSuite',
description: `Disfruta de la sincronización automática y reduce las entradas manuales con la integración Expensify + NetSuite. Obtén información financiera en profundidad y en tiempo real con la compatibilidad nativa y personalizada con segmentos, incluida la asignación de proyectos y clientes.`,
onlyAvailableOnPlan: 'Nuestra integración NetSuite sólo está disponible en el plan Control, a partir de ',
},
sageIntacct: {
title: 'Sage Intacct',
description: `Disfruta de una sincronización automatizada y reduce las entradas manuales con la integración Expensify + Sage Intacct. Obtén información financiera en profundidad y en tiempo real con dimensiones definidas por el usuario, así como codificación de gastos por departamento, clase, ubicación, cliente y proyecto (trabajo).`,
onlyAvailableOnPlan: 'Nuestra integración Sage Intacct sólo está disponible en el plan Control, a partir de ',
},
glCodes: {
title: 'Códigos de libro mayor',
description: `Añada códigos de libro mayor a sus categorías para exportar fácilmente los gastos a sus sistemas de contabilidad y nómina.`,
onlyAvailableOnPlan: 'Los códigos de libro mayor solo están disponibles en el plan Control, a partir de ',
},
glAndPayrollCodes: {
title: 'Códigos de libro mayor y nómina',
description: `Añada códigos de libro mayor y nómina a sus categorías para exportar fácilmente los gastos a sus sistemas de contabilidad y nómina.`,
onlyAvailableOnPlan: 'Los códigos de libro mayor y nómina solo están disponibles en el plan Control, a partir de ',
},
note: {
upgradeWorkspace: 'Mejore su espacio de trabajo para acceder a esta función, o',
learnMore: 'más información',
aboutOurPlans: 'sobre nuestros planes y precios.',
},
pricing: {
amount: '$9 ',
perActiveMember: 'por miembro activo al mes.',
},
upgradeToUnlock: 'Desbloquear esta función',
completed: {
headline: 'Has mejorado tu espacio de trabajo.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@ import type {FullScreenName} from '@libs/Navigation/types';
import SCREENS from '@src/SCREENS';

const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
[SCREENS.WORKSPACE.PROFILE]: [
SCREENS.WORKSPACE.NAME,
SCREENS.WORKSPACE.ADDRESS,
SCREENS.WORKSPACE.CURRENCY,
SCREENS.WORKSPACE.DESCRIPTION,
SCREENS.WORKSPACE.SHARE,
SCREENS.WORKSPACE.UPGRADE,
],
[SCREENS.WORKSPACE.PROFILE]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.ADDRESS, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.DESCRIPTION, SCREENS.WORKSPACE.SHARE],
[SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT, SCREENS.WORKSPACE.RATE_AND_UNIT_RATE, SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT],
[SCREENS.WORKSPACE.MEMBERS]: [
SCREENS.WORKSPACE.INVITE,
Expand Down
8 changes: 8 additions & 0 deletions src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {FullScreenNavigatorParamList} from '@libs/Navigation/types';
import {isControlPolicy} from '@libs/PolicyUtils';
import * as Category from '@userActions/Policy/Category';
import * as DistanceRate from '@userActions/Policy/DistanceRate';
import * as Policy from '@userActions/Policy/Policy';
Expand Down Expand Up @@ -204,6 +205,13 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
return;
}
if (isEnabled) {
if (!isControlPolicy(policy)) {
Navigation.navigate(
ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.reportFields.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)),
);
return;
}

Policy.enablePolicyReportFields(policyID, true);
return;
}
Expand Down
16 changes: 14 additions & 2 deletions src/pages/workspace/categories/CategorySettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,27 @@ function CategorySettingsPage({route, policyCategories, navigation}: CategorySet
<MenuItemWithTopDescription
title={policyCategory['GL Code']}
description={translate(`workspace.categories.glCode`)}
onPress={() => Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_GL_CODE.getRoute(route.params.policyID, policyCategory.name))}
onPress={() => {
if (!isControlPolicy(policy)) {
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(route.params.policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.glAndPayrollCodes.alias));
return;
}
Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_GL_CODE.getRoute(route.params.policyID, policyCategory.name));
}}
shouldShowRightIcon
/>
</OfflineWithFeedback>
<OfflineWithFeedback pendingAction={policyCategory.pendingFields?.['Payroll Code']}>
<MenuItemWithTopDescription
title={policyCategory['Payroll Code']}
description={translate(`workspace.categories.payrollCode`)}
onPress={() => Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_PAYROLL_CODE.getRoute(route.params.policyID, policyCategory.name))}
onPress={() => {
if (!isControlPolicy(policy)) {
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(route.params.policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.glAndPayrollCodes.alias));
return;
}
Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_PAYROLL_CODE.getRoute(route.params.policyID, policyCategory.name));
}}
shouldShowRightIcon
disabled={shouldDisablePayrollCode}
/>
Expand Down
4 changes: 4 additions & 0 deletions src/pages/workspace/tags/TagSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ function TagSettingsPage({route, policyTags, navigation}: TagSettingsPageProps)
};

const navigateToEditGlCode = () => {
if (!PolicyUtils.isControlPolicy(policy)) {
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(route.params.policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.glCodes.alias));
return;
}
Navigation.navigate(ROUTES.WORKSPACE_TAG_GL_CODE.getRoute(route.params.policyID, route.params.orderWeight, currentPolicyTag.name));
};

Expand Down
12 changes: 7 additions & 5 deletions src/pages/workspace/upgrade/UpgradeIntro.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import {View} from 'react-native';
import type {ValueOf} from 'type-fest';
import Badge from '@components/Badge';
import Button from '@components/Button';
import Icon from '@components/Icon';
Expand All @@ -18,21 +19,22 @@ import ROUTES from '@src/ROUTES';
type Props = {
buttonDisabled?: boolean;
loading?: boolean;
feature: (typeof CONST.UPGRADE_FEATURE_INTRO_MAPPING)[0];
feature: ValueOf<typeof CONST.UPGRADE_FEATURE_INTRO_MAPPING>;
onUpgrade: () => void;
};

function UpgradeIntro({feature, onUpgrade, buttonDisabled, loading}: Props) {
const styles = useThemeStyles();
const {isExtraSmallScreenWidth, isSmallScreenWidth} = useResponsiveLayout();
const {translate} = useLocalize();
const iconSrc = feature.icon in Illustrations ? Illustrations[feature.icon as keyof typeof Illustrations] : Expensicon[feature.icon as keyof typeof Expensicon];

return (
<View style={styles.p5}>
<View style={styles.workspaceUpgradeIntroBox({isExtraSmallScreenWidth, isSmallScreenWidth})}>
<View style={[styles.mb3, styles.flexRow, styles.justifyContentBetween]}>
<Icon
src={Illustrations[feature.icon]}
src={iconSrc}
width={variables.iconSizeExtraLarge}
height={variables.iconSizeExtraLarge}
/>
Expand All @@ -46,9 +48,9 @@ function UpgradeIntro({feature, onUpgrade, buttonDisabled, loading}: Props) {
<Text style={[styles.textHeadlineH1, styles.mb4]}>{translate(feature.title)}</Text>
<Text style={[styles.textNormal, styles.textSupporting, styles.mb4]}>{translate(feature.description)}</Text>
<Text style={[styles.textNormal, styles.textSupporting]}>
{translate(`workspace.upgrade.${feature.id}.pricing.onlyAvailableOnPlan`)}
<Text style={[styles.themeTextColor, styles.textBold]}>{translate(`workspace.upgrade.${feature.id}.pricing.amount`)}</Text>
{translate(`workspace.upgrade.${feature.id}.pricing.perActiveMember`)}
{translate(`workspace.upgrade.${feature.id}.onlyAvailableOnPlan`)}
<Text style={[styles.themeTextColor, styles.textBold]}>{translate(`workspace.upgrade.pricing.amount`)}</Text>
{translate(`workspace.upgrade.pricing.perActiveMember`)}
</Text>
</View>
<Button
Expand Down
Loading
Loading