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

Downgrade Feature #54356

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,7 @@ const CONST = {
UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE',
LEAVE_POLICY: 'POLICYCHANGELOG_LEAVE_POLICY',
CORPORATE_UPGRADE: 'POLICYCHANGELOG_CORPORATE_UPGRADE',
TEAM_DOWNGRADE: 'POLICYCHANGELOG_TEAM_DOWNGRADE',
},
ROOM_CHANGE_LOG: {
INVITE_TO_ROOM: 'INVITETOROOM',
Expand Down
22 changes: 22 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ const translations = {
drafts: 'Drafts',
finished: 'Finished',
upgrade: 'Upgrade',
downgradeWorkspace: 'Downgrade workspace',
companyID: 'Company ID',
userID: 'User ID',
disable: 'Disable',
Expand Down Expand Up @@ -4382,6 +4383,27 @@ const translations = {
},
},
},
downgrade: {
commonFeatures: {
title: 'Downgrade to the Collect plan',
note: 'If you downgrade, you’ll lose access to these features and more:',
benefits: {
note: 'For a full comparison of our plans, check out our',
pricingPage: 'pricing page',
confirm: 'Are you sure you want to downgrade and remove your configurations?',
warning: 'This cannot be undone.',
benefit1: 'Accounting connections (except QuickBooks Online and Xero)',
benefit2: 'Smart expense rules',
benefit3: 'Multi-level approval workflows',
benefit4: 'Enhanced security controls',
},
},
completed: {
headline: 'Your workspace has been downgraded',
description: 'You have other workspace on the Control plan. To be billed at the Collect rate, you must downgrade all workspaces.',
gotIt: 'Got it, thanks',
},
},
restrictedAction: {
restricted: 'Restricted',
actionsAreCurrentlyRestricted: ({workspaceName}: ActionsAreCurrentlyRestricted) => `Actions on the ${workspaceName} workspace are currently restricted`,
Expand Down
22 changes: 22 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ const translations = {
drafts: 'Borradores',
finished: 'Finalizados',
upgrade: 'Mejora',
downgradeWorkspace: 'Desmejora tu espacio de trabajo',
companyID: 'Empresa ID',
userID: 'Usuario ID',
disable: 'Deshabilitar',
Expand Down Expand Up @@ -4449,6 +4450,27 @@ const translations = {
},
},
},
downgrade: {
DylanDylann marked this conversation as resolved.
Show resolved Hide resolved
commonFeatures: {
title: 'Desmejorar al plan Recopilar',
note: 'Si desmejoras, perderás acceso a estas funciones y más:',
benefits: {
note: 'Para una comparación completa de nuestros planes, consulta nuestra',
pricingPage: 'página de precios',
confirm: '¿Estás seguro de que deseas desmejorar y eliminar tus configuraciones?',
warning: 'Esto no se puede deshacer.',
benefit1: 'Conexiones de contabilidad (excepto QuickBooks Online y Xero)',
benefit2: 'Reglas inteligentes de gastos',
benefit3: 'Flujos de aprobación de varios niveles',
benefit4: 'Controles de seguridad mejorados',
},
},
completed: {
headline: 'Tu espacio de trabajo ha sido bajado de categoría',
description: 'Tienes otro espacio de trabajo en el plan Controlar. Para facturarte con la tasa del plan Recopilar, debes bajar de categoría todos los espacios de trabajo.',
gotIt: 'Entendido, gracias.',
},
},
restrictedAction: {
restricted: 'Restringido',
actionsAreCurrentlyRestricted: ({workspaceName}: ActionsAreCurrentlyRestricted) => `Las acciones en el espacio de trabajo ${workspaceName} están actualmente restringidas`,
Expand Down
5 changes: 5 additions & 0 deletions src/libs/API/parameters/DowngradeToTeamParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type DowngradeToTeamParams = {
policyID: string;
};

export default DowngradeToTeamParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ export type {default as UpdateSubscriptionSizeParams} from './UpdateSubscription
export type {default as ReportExportParams} from './ReportExportParams';
export type {default as MarkAsExportedParams} from './MarkAsExportedParams';
export type {default as UpgradeToCorporateParams} from './UpgradeToCorporateParams';
export type {default as DowngradeToTeamParams} from './DowngradeToTeamParams';
export type {default as DeleteMoneyRequestOnSearchParams} from './DeleteMoneyRequestOnSearchParams';
export type {default as HoldMoneyRequestOnSearchParams} from './HoldMoneyRequestOnSearchParams';
export type {default as ApproveMoneyRequestOnSearchParams} from './ApproveMoneyRequestOnSearchParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ const WRITE_COMMANDS = {
REPORT_EXPORT: 'Report_Export',
MARK_AS_EXPORTED: 'MarkAsExported',
UPGRADE_TO_CORPORATE: 'UpgradeToCorporate',
DOWNGRADE_TO_TEAM: 'Policy_DowngradeToTeam',
DELETE_MONEY_REQUEST_ON_SEARCH: 'DeleteMoneyRequestOnSearch',
HOLD_MONEY_REQUEST_ON_SEARCH: 'HoldMoneyRequestOnSearch',
APPROVE_MONEY_REQUEST_ON_SEARCH: 'ApproveMoneyRequestOnSearch',
Expand Down Expand Up @@ -792,6 +793,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UPDATE_SAGE_INTACCT_SYNC_REIMBURSEMENT_ACCOUNT_ID]: Parameters.UpdateSageIntacctGenericTypeParams<'vendorID', string>;

[WRITE_COMMANDS.UPGRADE_TO_CORPORATE]: Parameters.UpgradeToCorporateParams;
[WRITE_COMMANDS.DOWNGRADE_TO_TEAM]: Parameters.DowngradeToTeamParams;

// Netsuite parameters
[WRITE_COMMANDS.UPDATE_NETSUITE_SUBSIDIARY]: Parameters.UpdateNetSuiteSubsidiaryParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.CATEGORIES_IMPORT]: () => require<ReactComponentModule>('../../../../pages/workspace/categories/ImportCategoriesPage').default,
[SCREENS.WORKSPACE.CATEGORIES_IMPORTED]: () => require<ReactComponentModule>('../../../../pages/workspace/categories/ImportedCategoriesPage').default,
[SCREENS.WORKSPACE.UPGRADE]: () => require<ReactComponentModule>('../../../../pages/workspace/upgrade/WorkspaceUpgradePage').default,
[SCREENS.WORKSPACE.DOWNGRADE]: () => require<ReactComponentModule>('../../../../pages/workspace/downgrade/WorkspaceDowngradePage').default,
[SCREENS.WORKSPACE.MEMBER_DETAILS]: () => require<ReactComponentModule>('../../../../pages/workspace/members/WorkspaceMemberDetailsPage').default,
[SCREENS.WORKSPACE.MEMBER_NEW_CARD]: () => require<ReactComponentModule>('../../../../pages/workspace/members/WorkspaceMemberNewCardPage').default,
[SCREENS.WORKSPACE.OWNER_CHANGE_CHECK]: () => require<ReactComponentModule>('@pages/workspace/members/WorkspaceOwnerChangeWrapperPage').default,
Expand Down
9 changes: 0 additions & 9 deletions src/libs/Permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,6 @@ function canUseLinkPreviews(): boolean {
return false;
}

/**
* Workspace downgrade is temporarily disabled
* API is being integrated in this GH issue https://github.com/Expensify/App/issues/51494
*/
function canUseWorkspaceDowngrade() {
return false;
}

export default {
canUseDefaultRooms,
canUseLinkPreviews,
Expand All @@ -63,6 +55,5 @@ export default {
canUseCombinedTrackSubmit,
canUseCategoryAndTagApprovers,
canUsePerDiem,
canUseWorkspaceDowngrade,
shouldShowProductTrainingElements,
};
11 changes: 11 additions & 0 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ function isControlPolicy(policy: OnyxEntry<Policy>): boolean {
return policy?.type === CONST.POLICY.TYPE.CORPORATE;
}

function isCollectPolicy(policy: OnyxEntry<Policy>): boolean {
return policy?.type === CONST.POLICY.TYPE.TEAM;
}

function isTaxTrackingEnabled(isPolicyExpenseChat: boolean, policy: OnyxEntry<Policy>, isDistanceRequest: boolean): boolean {
const distanceUnit = getDistanceRateCustomUnit(policy);
const customUnitID = distanceUnit?.customUnitID ?? CONST.DEFAULT_NUMBER_ID;
Expand Down Expand Up @@ -1165,6 +1169,11 @@ function areAllGroupPoliciesExpenseChatDisabled(policies = allPolicies) {
return !groupPolicies.some((policy) => !!policy?.isPolicyExpenseChatEnabled);
}

function hasOtherControlWorkspaces(currentPolicyID: string) {
const otherControlWorkspaces = Object.values(allPolicies ?? {}).filter((policy) => policy?.id !== currentPolicyID && isPolicyAdmin(policy) && isControlPolicy(policy));
return otherControlWorkspaces.length > 0;
}

export {
canEditTaxRate,
extractPolicyIDFromPath,
Expand Down Expand Up @@ -1265,6 +1274,7 @@ export {
getApprovalWorkflow,
getReimburserAccountID,
isControlPolicy,
isCollectPolicy,
isNetSuiteCustomSegmentRecord,
getNameFromNetSuiteCustomField,
isNetSuiteCustomFieldPropertyEditable,
Expand All @@ -1288,6 +1298,7 @@ export {
getUserFriendlyWorkspaceType,
isPolicyAccessible,
areAllGroupPoliciesExpenseChatDisabled,
hasOtherControlWorkspaces,
getManagerAccountEmail,
getRuleApprovers,
};
Expand Down
41 changes: 41 additions & 0 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
DeleteWorkspaceAvatarParams,
DeleteWorkspaceParams,
DisablePolicyBillableModeParams,
DowngradeToTeamParams,
EnablePolicyAutoApprovalOptionsParams,
EnablePolicyAutoReimbursementLimitParams,
EnablePolicyCompanyCardsParams,
Expand Down Expand Up @@ -3447,6 +3448,45 @@ function upgradeToCorporate(policyID: string, featureName?: string) {
API.write(WRITE_COMMANDS.UPGRADE_TO_CORPORATE, parameters, {optimisticData, successData, failureData});
}

function downgradeToTeam(policyID: string) {
const policy = getPolicy(policyID);
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `policy_${policyID}`,
value: {
isPendingDowngrade: true,
type: CONST.POLICY.TYPE.TEAM,
},
},
];

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

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `policy_${policyID}`,
value: {
isPendingUpgrade: false,
DylanDylann marked this conversation as resolved.
Show resolved Hide resolved
type: policy?.type,
},
},
];

const parameters: DowngradeToTeamParams = {policyID};

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

function setWorkspaceDefaultSpendCategory(policyID: string, groupID: string, category: string) {
const policy = getPolicy(policyID);
if (!policy) {
Expand Down Expand Up @@ -4728,6 +4768,7 @@ export {
updateInvoiceCompanyName,
updateInvoiceCompanyWebsite,
getAssignedSupportData,
downgradeToTeam,
};

export type {NewCustomUnit};
4 changes: 2 additions & 2 deletions src/pages/workspace/WorkspaceProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function WorkspaceProfilePage({policyDraft, policy: policyProp, route}: Workspac
const {shouldUseNarrowLayout} = useResponsiveLayout();
const illustrations = useThemeIllustrations();
const {activeWorkspaceID, setActiveWorkspaceID} = useActiveWorkspace();
const {canUseSpotnanaTravel, canUseWorkspaceDowngrade} = usePermissions();
const {canUseSpotnanaTravel} = usePermissions();

const [currencyList = {}] = useOnyx(ONYXKEYS.CURRENCY_LIST);
const [currentUserAccountID = -1] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID});
Expand Down Expand Up @@ -328,7 +328,7 @@ function WorkspaceProfilePage({policyDraft, policy: policyProp, route}: Workspac
</OfflineWithFeedback>
)}

{!!canUseWorkspaceDowngrade && !readOnly && !!policy?.type && (
{!readOnly && !!policy?.type && (
<OfflineWithFeedback pendingAction={policy?.pendingFields?.type}>
<View>
<MenuItemWithTopDescription
Expand Down
13 changes: 9 additions & 4 deletions src/pages/workspace/WorkspaceProfilePlanTypePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,18 @@ function WorkspaceProfilePlanTypePage({policy}: WithPolicyProps) {
) : null;

const handleUpdatePlan = () => {
if (policy?.type === currentPlan) {
Navigation.goBack();
if (policyID && policy?.type === CONST.POLICY.TYPE.TEAM && currentPlan === CONST.POLICY.TYPE.CORPORATE) {
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID));
return;
}

if (policyID && policy?.type === CONST.POLICY.TYPE.TEAM && currentPlan === CONST.POLICY.TYPE.CORPORATE) {
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID));
if (policyID && policy?.type === CONST.POLICY.TYPE.CORPORATE && currentPlan === CONST.POLICY.TYPE.TEAM) {
Navigation.navigate(ROUTES.WORKSPACE_DOWNGRADE.getRoute(policyID));
return;
}

if (policy?.type === currentPlan) {
Navigation.goBack();
}
};

Expand Down
28 changes: 28 additions & 0 deletions src/pages/workspace/downgrade/DowngradeConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import ConfirmationPage from '@components/ConfirmationPage';
import {MushroomTopHat} from '@components/Icon/Illustrations';
import useLocalize from '@hooks/useLocalize';
import * as PolicyUtils from '@libs/PolicyUtils';

type Props = {
onConfirmDowngrade: () => void;
policyID: string;
};

function DowngradeConfirmation({onConfirmDowngrade, policyID}: Props) {
const {translate} = useLocalize();
const hasOtherControlWorkspaces = PolicyUtils.hasOtherControlWorkspaces(policyID);

return (
<ConfirmationPage
heading={translate('workspace.downgrade.completed.headline')}
description={hasOtherControlWorkspaces ? translate('workspace.downgrade.completed.description') : undefined}
illustration={MushroomTopHat}
shouldShowButton
onButtonPress={onConfirmDowngrade}
buttonText={translate('workspace.downgrade.completed.gotIt')}
/>
);
}

export default DowngradeConfirmation;
82 changes: 82 additions & 0 deletions src/pages/workspace/downgrade/DowngradeIntro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';
import {View} from 'react-native';
import Button from '@components/Button';
import Icon from '@components/Icon';
import * as Illustrations from '@components/Icon/Illustrations';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useEnvironment from '@hooks/useEnvironment';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {openLink} from '@libs/actions/Link';
import CONST from '@src/CONST';

type Props = {
buttonDisabled?: boolean;
loading?: boolean;
onDowngrade: () => void;
};

function DowngradeIntro({onDowngrade, buttonDisabled, loading}: Props) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {environmentURL} = useEnvironment();
const {isExtraSmallScreenWidth} = useResponsiveLayout();

const benefits = [
translate('workspace.downgrade.commonFeatures.benefits.benefit1'),
translate('workspace.downgrade.commonFeatures.benefits.benefit2'),
translate('workspace.downgrade.commonFeatures.benefits.benefit3'),
translate('workspace.downgrade.commonFeatures.benefits.benefit4'),
];

return (
<View style={[styles.m5, styles.workspaceUpgradeIntroBox({isExtraSmallScreenWidth})]}>
<View style={[styles.mb3]}>
<Icon
src={Illustrations.Mailbox}
width={48}
height={48}
/>
</View>
<View style={styles.mb5}>
<Text style={[styles.textHeadlineH1, styles.mb4]}>{translate('workspace.downgrade.commonFeatures.title')}</Text>
<Text style={[styles.textNormal, styles.textSupporting, styles.mb4]}>{translate('workspace.downgrade.commonFeatures.note')}</Text>
{benefits.map((benefit) => (
<View
key={benefit}
style={[styles.pl2, styles.flexRow]}
>
<Text style={[styles.textNormal, styles.textSupporting]}>• </Text>
<Text style={[styles.textNormal, styles.textSupporting]}>{benefit}</Text>
</View>
))}
<Text style={[styles.textNormal, styles.textSupporting, styles.mt4]}>
{translate('workspace.downgrade.commonFeatures.benefits.note')}{' '}
<TextLink
style={[styles.link]}
onPress={() => openLink(CONST.PLAN_TYPES_AND_PRICING_HELP_URL, environmentURL)}
>
{translate('workspace.downgrade.commonFeatures.benefits.pricingPage')}
</TextLink>
.
</Text>
<Text style={[styles.mv4]}>
<Text style={[styles.textNormal, styles.textSupporting]}>{translate('workspace.downgrade.commonFeatures.benefits.confirm')}</Text>{' '}
<Text style={[styles.textBold, styles.textSupporting]}>{translate('workspace.downgrade.commonFeatures.benefits.warning')}</Text>
</Text>
</View>
<Button
isLoading={loading}
text={translate('common.downgradeWorkspace')}
success
onPress={onDowngrade}
isDisabled={buttonDisabled}
large
/>
</View>
);
}

export default DowngradeIntro;
Loading
Loading