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

Add workspace upgrade flow #43822

Merged
merged 20 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
10 changes: 10 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1985,6 +1985,9 @@ const CONST = {
PAID: 'paid',
ADMIN: 'admin',
},
DEFAULT_MAX_EXPENSE_AGE: 90,
DEFAULT_MAX_EXPENSE_AMOUNT: 200000,
DEFAULT_MAX_AMOUNT_NO_RECEIPT: 2500,
},

CUSTOM_UNITS: {
Expand Down Expand Up @@ -5058,6 +5061,13 @@ const CONST = {
},

EXCLUDE_FROM_LAST_VISITED_PATH: [SCREENS.NOT_FOUND, SCREENS.SAML_SIGN_IN, SCREENS.VALIDATE_LOGIN] as string[],
UPGRADE_FEATURE_INTRO_MAPPING: {
reportFields: {
title: 'workspace.upgrade.reportFields.title',
description: 'workspace.upgrade.reportFields.description',
icon: 'Tag',
},
},
} as const;

type Country = keyof typeof CONST.ALL_COUNTRIES;
Expand Down
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/categories/:categoryName',
getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}` as const,
},
WORKSPACE_UPGRADE: {
route: 'settings/workspaces/:policyID/upgrade/:featureName',
getRoute: (policyID: string, featureName: string) => `settings/workspaces/${policyID}/upgrade/${encodeURIComponent(featureName)}` as const,
},
WORKSPACE_CATEGORIES_SETTINGS: {
route: 'settings/workspaces/:policyID/categories/settings',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories/settings` as const,
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ const SCREENS = {
DISTANCE_RATE_EDIT: 'Distance_Rate_Edit',
DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT: 'Distance_Rate_Tax_Reclaimable_On_Edit',
DISTANCE_RATE_TAX_RATE_EDIT: 'Distance_Rate_Tax_Rate_Edit',
UPGRADE: 'Workspace_Upgrade',
},

EDIT_REQUEST: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/ConfirmationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type ConfirmationPageProps = {
heading: string;

/** Description of the confirmation page */
description: string;
description: React.ReactNode;

/** The text for the button label */
buttonText?: string;
Expand Down
21 changes: 21 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ export default {
shared: 'Shared',
drafts: 'Drafts',
finished: 'Finished',
upgrade: 'Upgrade',
companyID: 'Company ID',
userID: 'User ID',
disable: 'Disable',
Expand Down Expand Up @@ -2890,6 +2891,26 @@ export default {
errorDescriptionPartTwo: 'reach out to Concierge',
errorDescriptionPartThree: 'for help.',
},
upgrade: {
reportFields: {
// Todo: Add proper translations for the following strings
title: 'Report fields',
description: 'Set up custom fields for spend.',
},
note: {
upgradeWorkspace: 'Upgrade your workspace to access this feature, or',
learnMore: 'learn more',
aboutOurPlans: 'about our plans and pricing.',
},
upgradeToUnlock: 'Unlock this feature',
completed: {
headline: `You've upgraded your workspace!`,
successMessage: (policyName: string) => `You've successfully upgraded your ${policyName} workspace to the Control plan!`,
viewSubscription: 'View your subscription',
moreDetails: 'for more details.',
gotIt: 'Got it, thanks',
},
},
restrictedAction: {
restricted: 'Restricted',
actionsAreCurrentlyRestricted: ({workspaceName}) => `Actions on the ${workspaceName} workspace are currently restricted`,
Expand Down
21 changes: 21 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ export default {
shared: 'Compartidos',
drafts: 'Borradores',
finished: 'Finalizados',
upgrade: 'Mejora',
companyID: 'Empresa ID',
userID: 'Usuario ID',
disable: 'Deshabilitar',
Expand Down Expand Up @@ -2924,6 +2925,26 @@ export default {
errorDescriptionPartTwo: 'contacta con el conserje',
errorDescriptionPartThree: 'por ayuda.',
},
upgrade: {
reportFields: {
// Todo: Add proper translations for the following strings
title: 'Report fields',
description: 'Set up custom fields for spend.',
allroundexperts marked this conversation as resolved.
Show resolved Hide resolved
},
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.',
},
upgradeToUnlock: 'Desbloquear esta función',
completed: {
headline: 'Has mejorado tu espacio de trabajo.',
successMessage: (policyName: string) => `Ha mejorado correctamente su espacio de trabajo ${policyName} al plan Control.`,
viewSubscription: 'Ver su suscripción',
moreDetails: 'para obtener más información.',
gotIt: 'Entendido, gracias.',
},
},
restrictedAction: {
restricted: 'Restringido',
actionsAreCurrentlyRestricted: ({workspaceName}) => `Las acciones en el espacio de trabajo ${workspaceName} están actualmente restringidas`,
Expand Down
6 changes: 6 additions & 0 deletions src/libs/API/parameters/UpgradeToCorporateParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type UpgradeToCorporateParams = {
policyID: string;
featureName: string;
};

export default UpgradeToCorporateParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,6 @@ export type {default as UpdateSubscriptionAutoRenewParams} from './UpdateSubscri
export type {default as UpdateSubscriptionAddNewUsersAutomaticallyParams} from './UpdateSubscriptionAddNewUsersAutomaticallyParams';
export type {default as GenerateSpotnanaTokenParams} from './GenerateSpotnanaTokenParams';
export type {default as UpdateSubscriptionSizeParams} from './UpdateSubscriptionSizeParams';
export type {default as UpgradeToCorporateParams} from './UpgradeToCorporateParams';
export type {default as UpdateNetSuiteSubsidiaryParams} from './UpdateNetSuiteSubsidiaryParams';
export type {default as UpdateNetSuiteGenericTypeParams} from './UpdateNetSuiteGenericTypeParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ const WRITE_COMMANDS = {
UPDATE_SUBSCRIPTION_AUTO_RENEW: 'UpdateSubscriptionAutoRenew',
UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY: 'UpdateSubscriptionAddNewUsersAutomatically',
UPDATE_SUBSCRIPTION_SIZE: 'UpdateSubscriptionSize',
UPGRADE_TO_CORPORATE: 'UpgradeToCorporate',
UPDATE_NETSUITE_SUBSIDIARY: 'UpdateNetSuiteSubsidiary',
UPDATE_NETSUITE_EXPORTER: 'UpdateNetSuiteExporter',
UPDATE_NETSUITE_EXPORT_DATE: 'UpdateNetSuiteExportDate',
Expand Down Expand Up @@ -477,6 +478,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE]: Parameters.UpdateSubscriptionSizeParams;
[WRITE_COMMANDS.CONNECT_POLICY_TO_SAGE_INTACCT]: Parameters.ConnectPolicyToSageIntacctParams;

[WRITE_COMMANDS.UPGRADE_TO_CORPORATE]: Parameters.UpgradeToCorporateParams;
// Netsuite parameters
[WRITE_COMMANDS.UPDATE_NETSUITE_SUBSIDIARY]: Parameters.UpdateNetSuiteSubsidiaryParams;
[WRITE_COMMANDS.UPDATE_NETSUITE_EXPORTER]: Parameters.UpdateNetSuiteGenericTypeParams<'email', string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require<ReactComponentModule>('../../../../pages/workspace/categories/CategorySettingsPage').default,
[SCREENS.WORKSPACE.ADDRESS]: () => require<ReactComponentModule>('../../../../pages/workspace/WorkspaceProfileAddressPage').default,
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require<ReactComponentModule>('../../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default,
[SCREENS.WORKSPACE.UPGRADE]: () => require<ReactComponentModule>('../../../../pages/workspace/upgrade/WorkspaceUpgradePage').default,
[SCREENS.WORKSPACE.MEMBER_DETAILS]: () => require<ReactComponentModule>('../../../../pages/workspace/members/WorkspaceMemberDetailsPage').default,
[SCREENS.WORKSPACE.OWNER_CHANGE_CHECK]: () => require<ReactComponentModule>('@pages/workspace/members/WorkspaceOwnerChangeWrapperPage').default,
[SCREENS.WORKSPACE.OWNER_CHANGE_SUCCESS]: () => require<ReactComponentModule>('../../../../pages/workspace/members/WorkspaceOwnerChangeSuccessPage').default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ 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.PROFILE]: [
SCREENS.WORKSPACE.NAME,
SCREENS.WORKSPACE.ADDRESS,
SCREENS.WORKSPACE.CURRENCY,
SCREENS.WORKSPACE.DESCRIPTION,
SCREENS.WORKSPACE.SHARE,
SCREENS.WORKSPACE.UPGRADE,
],
[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
6 changes: 6 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
categoryName: (categoryName: string) => decodeURIComponent(categoryName),
},
},
[SCREENS.WORKSPACE.UPGRADE]: {
path: ROUTES.WORKSPACE_UPGRADE.route,
parse: {
featureName: (featureName: string) => decodeURIComponent(featureName),
},
},
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: {
path: ROUTES.WORKSPACE_CATEGORIES_SETTINGS.route,
},
Expand Down
5 changes: 5 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ type SettingsNavigatorParamList = {
categoryName: string;
backTo?: Routes;
};
[SCREENS.WORKSPACE.UPGRADE]: {
policyID: string;
featureName: string;
backTo?: Routes;
};
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: {
policyID: string;
backTo?: Routes;
Expand Down
55 changes: 55 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import type {
UpdateWorkspaceCustomUnitAndRateParams,
UpdateWorkspaceDescriptionParams,
UpdateWorkspaceGeneralSettingsParams,
UpgradeToCorporateParams,
} from '@libs/API/parameters';
import type UpdatePolicyAddressParams from '@libs/API/parameters/UpdatePolicyAddressParams';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
Expand Down Expand Up @@ -2977,6 +2978,59 @@ function setForeignCurrencyDefault(policyID: string, taxCode: string) {
API.write(WRITE_COMMANDS.SET_POLICY_TAXES_FOREIGN_CURRENCY_DEFAULT, parameters, onyxData);
}

function upgradeToCorporate(policyID: string, featureName: string) {
const policy = getPolicy(policyID);
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `policy_${policyID}`,
value: {
isPendingUpgrade: true,
Copy link
Contributor

@youssef-lr youssef-lr Jun 26, 2024

Choose a reason for hiding this comment

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

let's add these optimistic values and their keys in CONST if they're not there:

type=CORPORATE
maxExpenseAge=CONST.POLICY.DEFAULT_MAX_EXPENSE_AGE=90
maxExpenseAmount=CONST.POLICY.DEFAULT_MAX_EXPENSE_AMOUNT=200000
maxExpenseAmountNoReceipt=CONST.POLICY.DEFAULT_MAX_AMOUNT_NO_RECEIPT=2500
glCodes=true

And then add these values to the optimistic data if instant submit is enabled:

if (isInstantSubmitEnabled(policy)) {
    // set autoReporting=false
    // set autoReportingFrequency=CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Handled.

type: CONST.POLICY.TYPE.CORPORATE,
maxExpenseAge: CONST.POLICY.DEFAULT_MAX_EXPENSE_AGE,
maxExpenseAmount: CONST.POLICY.DEFAULT_MAX_EXPENSE_AMOUNT,
maxExpenseAmountNoReceipt: CONST.POLICY.DEFAULT_MAX_AMOUNT_NO_RECEIPT,
glCodes: true,
...(PolicyUtils.isInstantSubmitEnabled(policy) && {
autoReporting: true,
autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL,
}),
},
},
];

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `policy_${policyID}`,
value: {
isPendingUpgrade: false,
},
},
];

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `policy_${policyID}`,
value: {
isPendingUpgrade: false,
type: policy?.type,
maxExpenseAge: policy?.maxExpenseAge ?? null,
maxExpenseAmount: policy?.maxExpenseAmount ?? null,
maxExpenseAmountNoReceipt: policy?.maxExpenseAmountNoReceipt ?? null,
glCodes: policy?.glCodes ?? null,
autoReporting: policy?.autoReporting ?? null,
autoReportingFrequency: policy?.autoReportingFrequency ?? null,
},
},
];

const parameters: UpgradeToCorporateParams = {policyID, featureName};

API.write(WRITE_COMMANDS.UPGRADE_TO_CORPORATE, parameters, {optimisticData, successData, failureData});
}

function getPoliciesConnectedToSageIntacct(): Policy[] {
return Object.values(allPolicies ?? {}).filter<Policy>((policy): policy is Policy => !!policy && !!policy?.connections?.intacct);
}
Expand Down Expand Up @@ -3043,6 +3097,7 @@ export {
buildPolicyData,
enableExpensifyCard,
createPolicyExpenseChats,
upgradeToCorporate,
getPoliciesConnectedToSageIntacct,
};

Expand Down
40 changes: 40 additions & 0 deletions src/pages/workspace/upgrade/UpgradeConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import ConfirmationPage from '@components/ConfirmationPage';
import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import ROUTES from '@src/ROUTES';

type Props = {
policyName: string;
policyID: string;
};

function UpgradeConfirmation({policyName, policyID}: Props) {
const {translate} = useLocalize();
const styles = useThemeStyles();

return (
<ConfirmationPage
heading={translate('workspace.upgrade.completed.headline')}
description={
<>
{translate('workspace.upgrade.completed.successMessage', policyName)}{' '}
<TextLink
style={styles.link}
onPress={() => Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION)}
>
{translate('workspace.upgrade.completed.viewSubscription')}
</TextLink>{' '}
{translate('workspace.upgrade.completed.moreDetails')}
</>
}
shouldShowButton
onButtonPress={() => Navigation.goBack(ROUTES.WORKSPACE_PROFILE.getRoute(policyID))}
buttonText={translate('workspace.upgrade.completed.gotIt')}
/>
);
}

export default UpgradeConfirmation;
74 changes: 74 additions & 0 deletions src/pages/workspace/upgrade/UpgradeIntro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import {View} from 'react-native';
import Badge from '@components/Badge';
import Button from '@components/Button';
import Icon from '@components/Icon';
import * as Expensicon from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import ROUTES from '@src/ROUTES';

type Props = {
buttonDisabled?: boolean;
loading?: boolean;
title: string;
description: string;
icon: keyof typeof Illustrations;
onUpgrade: () => void;
};

function UpgradeIntro({title, description, icon, onUpgrade, buttonDisabled, loading}: Props) {
const styles = useThemeStyles();
const {isExtraSmallScreenWidth, isSmallScreenWidth} = useResponsiveLayout();
const {translate} = useLocalize();

return (
<View style={styles.p5}>
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry for not noticing this before but we do have a Section component can we reuse it here @allroundexperts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you provide the full name of this component?

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm... Would this be plug in play? I think we'll need to do some changes within this component to meet our needs.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm... Would this be plug in play? I think we'll need to do some changes within this component to meet our needs.

Ideally we should reuse but we def. need to make changes to component to meet our needs here, I dont think this is super important change, current component is readable as it is. Consider this as a optional comment 👍

<View style={styles.workspaceUpgradeIntroBox({isExtraSmallScreenWidth, isSmallScreenWidth})}>
<View style={[styles.mb3, styles.flexRow, styles.justifyContentBetween]}>
<Icon
src={Illustrations[icon]}
width={variables.iconSizeExtraLarge}
height={variables.iconSizeExtraLarge}
/>
<Badge
icon={Expensicon.Unlock}
text={translate('workspace.upgrade.upgradeToUnlock')}
success
/>
</View>
<View style={styles.mb5}>
<Text style={[styles.textHeadlineH1, styles.mb4]}>{title}</Text>
<Text style={[styles.textNormal, styles.textSupporting]}>{description}</Text>
</View>
<Button
isLoading={loading}
text={translate('common.upgrade')}
success
onPress={onUpgrade}
isDisabled={buttonDisabled}
/>
</View>
<View style={styles.mt6}>
<Text style={[styles.textNormal, styles.textSupporting]}>
{translate('workspace.upgrade.note.upgradeWorkspace')}{' '}
<TextLink
style={[styles.link]}
onPress={() => Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION)}
>
{translate('workspace.upgrade.note.learnMore')}
</TextLink>{' '}
{translate('workspace.upgrade.note.aboutOurPlans')}
</Text>
</View>
</View>
);
}

export default UpgradeIntro;
Loading
Loading