Skip to content

Commit

Permalink
Merge pull request #54356 from DylanDylann/downgrade-feat
Browse files Browse the repository at this point in the history
Downgrade Feature
  • Loading branch information
carlosmiceli authored Dec 23, 2024
2 parents 8529f39 + a16f2ac commit 6bbb031
Show file tree
Hide file tree
Showing 17 changed files with 310 additions and 16 deletions.
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 @@ -4376,6 +4377,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 @@ -4442,6 +4443,27 @@ const translations = {
},
},
},
downgrade: {
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 @@ -3450,6 +3451,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,
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 @@ -4731,6 +4771,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

0 comments on commit 6bbb031

Please sign in to comment.