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

[Free trial] Restrict actions throughout the App of "past due" billing owner #44393

Merged
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
7 changes: 7 additions & 0 deletions src/components/SettlementButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx, withOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import playSound, {SOUNDS} from '@libs/Sound';
import * as SubscriptionUtils from '@libs/SubscriptionUtils';
import * as BankAccounts from '@userActions/BankAccounts';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -227,6 +229,11 @@ function SettlementButton({
}, [currency, formattedAmount, iouReport, policyID, translate, shouldHidePaymentOptions, shouldShowApproveButton, shouldDisableApproveButton]);

const selectPaymentType = (event: KYCFlowEvent, iouPaymentType: PaymentMethodType, triggerKYCFlow: TriggerKYCFlow) => {
if (policy && SubscriptionUtils.shouldRestrictUserBillableActions(policy.id)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id));
return;
}

if (iouPaymentType === CONST.IOU.PAYMENT_TYPE.EXPENSIFY || iouPaymentType === CONST.IOU.PAYMENT_TYPE.VBBA) {
triggerKYCFlow(event, iouPaymentType);
BankAccounts.setPersonalBankAccountContinueKYCOnSuccess(ROUTES.ENABLE_PAYMENTS);
Expand Down
2 changes: 1 addition & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3379,7 +3379,7 @@ export default {
preTrial: {
title: 'Start a free trial',
subtitle: 'To get started, ',
subtitleLink: 'complete your setup checklist here',
subtitleLink: 'complete your setup checklist here.',
},
},
cardSection: {
Expand Down
2 changes: 1 addition & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3879,7 +3879,7 @@ export default {
preTrial: {
title: 'Iniciar una prueba gratuita',
subtitle: 'Para empezar, ',
subtitleLink: 'completa la lista de configuración aquí',
subtitleLink: 'completa la lista de configuración aquí.',
},
},
cardSection: {
Expand Down
24 changes: 20 additions & 4 deletions src/libs/SubscriptionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import Onyx from 'react-native-onyx';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {BillingGraceEndPeriod, Policy} from '@src/types/onyx';
import * as PolicyUtils from './PolicyUtils';

let currentUserAccountID = -1;
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (value) => {
currentUserAccountID = value?.accountID ?? -1;
},
});

let firstDayFreeTrial: OnyxEntry<string>;
Onyx.connect({
Expand Down Expand Up @@ -106,6 +115,8 @@ function doesUserHavePaymentCardAdded(): boolean {
function shouldRestrictUserBillableActions(policyID: string): boolean {
const currentDate = new Date();

const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];

// This logic will be executed if the user is a workspace's non-owner (normal user or admin).
// We should restrict the workspace's non-owner actions if it's member of a workspace where the owner is
// past due and is past its grace period end.
Expand All @@ -114,18 +125,23 @@ function shouldRestrictUserBillableActions(policyID: string): boolean {

if (userBillingGracePeriodEnd && isAfter(currentDate, fromUnixTime(userBillingGracePeriodEnd.value))) {
// Extracts the owner account ID from the collection member key.
const ownerAccountID = entryKey.slice(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END.length);
const ownerAccountID = Number(entryKey.slice(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END.length));

const ownerPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
if (String(ownerPolicy?.ownerAccountID ?? -1) === ownerAccountID) {
if (PolicyUtils.isPolicyOwner(policy, ownerAccountID)) {
return true;
}
}
}

// If it reached here it means that the user is actually the workspace's owner.
// We should restrict the workspace's owner actions if it's past its grace period end date and it's owing some amount.
if (ownerBillingGraceEndPeriod && amountOwed !== undefined && amountOwed > 0 && isAfter(currentDate, fromUnixTime(ownerBillingGraceEndPeriod))) {
if (
PolicyUtils.isPolicyOwner(policy, currentUserAccountID) &&
ownerBillingGraceEndPeriod &&
amountOwed !== undefined &&
amountOwed > 0 &&
isAfter(currentDate, fromUnixTime(ownerBillingGraceEndPeriod))
) {
return true;
}

Expand Down
16 changes: 16 additions & 0 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUReportAction, TransactionDetails} from '@libs/ReportUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as SubscriptionUtils from '@libs/SubscriptionUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import ViolationsUtils from '@libs/Violations/ViolationsUtils';
import type {IOUAction, IOUType} from '@src/CONST';
Expand Down Expand Up @@ -6218,6 +6219,11 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry<OnyxTypes.Report>, excludedI
}

function approveMoneyRequest(expenseReport: OnyxEntry<OnyxTypes.Report>, full?: boolean) {
if (expenseReport?.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(expenseReport.policyID)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(expenseReport.policyID));
return;
}

const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport?.reportID}`] ?? null;
let total = expenseReport?.total ?? 0;
const hasHeldExpenses = ReportUtils.hasHeldExpenses(expenseReport?.reportID);
Expand Down Expand Up @@ -6351,6 +6357,11 @@ function approveMoneyRequest(expenseReport: OnyxEntry<OnyxTypes.Report>, full?:
}

function submitReport(expenseReport: OnyxTypes.Report) {
if (expenseReport.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(expenseReport.policyID)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(expenseReport.policyID));
return;
}

const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null;
const parentReport = getReportOrDraftReport(expenseReport.parentReportID);
const policy = PolicyUtils.getPolicy(expenseReport.policyID);
Expand Down Expand Up @@ -6581,6 +6592,11 @@ function cancelPayment(expenseReport: OnyxTypes.Report, chatReport: OnyxTypes.Re
}

function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.Report, iouReport: OnyxTypes.Report, full = true) {
if (chatReport.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(chatReport.policyID)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(chatReport.policyID));
return;
}

const recipient = {accountID: iouReport.ownerAccountID};
const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType, full);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Report from '@libs/actions/Report';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import variables from '@styles/variables';
Expand All @@ -28,7 +27,7 @@ function WorkspaceAdminRestrictedAction({policyID}: WorkspaceAdminRestrictedActi

const openAdminsReport = useCallback(() => {
const reportID = `${PolicyUtils.getPolicy(policyID)?.chatReportIDAdmins}` ?? '-1';
Report.openReport(reportID);
Navigation.closeRHPFlow();
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID));
}, [policyID]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function WorkspaceOwnerRestrictedAction() {
const styles = useThemeStyles();

const addPaymentCard = () => {
Navigation.closeRHPFlow();
Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as Report from '@libs/actions/Report';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import variables from '@styles/variables';
Expand All @@ -28,7 +27,7 @@ function WorkspaceUserRestrictedAction({policyID}: WorkspaceUserRestrictedAction

const openPolicyExpenseReport = useCallback(() => {
const reportID = ReportUtils.findPolicyExpenseChatByPolicyID(policyID)?.reportID ?? '-1';
Report.openReport(reportID);
Navigation.closeRHPFlow();
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID));
}, [policyID]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
import getIconForAction from '@libs/getIconForAction';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import * as SubscriptionUtils from '@libs/SubscriptionUtils';
import * as IOU from '@userActions/IOU';
import * as Report from '@userActions/Report';
import * as Task from '@userActions/Task';
import type {IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';

type MoneyRequestOptions = Record<Exclude<IOUType, typeof CONST.IOU.TYPE.REQUEST | typeof CONST.IOU.TYPE.SEND>, PopoverMenuItem>;
Expand Down Expand Up @@ -125,31 +128,40 @@ function AttachmentPickerWithMenuItems({
* Returns the list of IOU Options
*/
const moneyRequestOptions = useMemo(() => {
const selectOption = (onSelected: () => void, shouldRestrictAction: boolean) => {
if (shouldRestrictAction && policy && SubscriptionUtils.shouldRestrictUserBillableActions(policy.id)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(policy.id));
return;
}

onSelected();
};

const options: MoneyRequestOptions = {
[CONST.IOU.TYPE.SPLIT]: {
icon: Expensicons.Transfer,
text: translate('iou.splitExpense'),
onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, report?.reportID ?? '-1'),
onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, report?.reportID ?? '-1'), true),
},
[CONST.IOU.TYPE.SUBMIT]: {
icon: getIconForAction(CONST.IOU.TYPE.REQUEST),
text: translate('iou.submitExpense'),
onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, report?.reportID ?? '-1'),
onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, report?.reportID ?? '-1'), true),
},
[CONST.IOU.TYPE.PAY]: {
icon: getIconForAction(CONST.IOU.TYPE.SEND),
text: translate('iou.paySomeone', {name: ReportUtils.getPayeeName(report)}),
onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, report?.reportID ?? '-1'),
onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, report?.reportID ?? '-1'), false),
},
[CONST.IOU.TYPE.TRACK]: {
icon: getIconForAction(CONST.IOU.TYPE.TRACK),
text: translate('iou.trackExpense'),
onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, report?.reportID ?? '-1'),
onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, report?.reportID ?? '-1'), true),
},
[CONST.IOU.TYPE.INVOICE]: {
icon: Expensicons.InvoiceGeneric,
text: translate('workspace.invoices.sendInvoice'),
onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.INVOICE, report?.reportID ?? '-1'),
onSelected: () => selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.INVOICE, report?.reportID ?? '-1'), false),
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import Navigation from '@libs/Navigation/Navigation';
import type {CentralPaneName, NavigationPartialRoute, RootStackParamList} from '@libs/Navigation/types';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as SubscriptionUtils from '@libs/SubscriptionUtils';
import * as App from '@userActions/App';
import * as IOU from '@userActions/IOU';
import * as Policy from '@userActions/Policy/Policy';
Expand Down Expand Up @@ -228,41 +229,54 @@ function FloatingActionButtonAndPopover(
}, [personalDetails, quickActionReport, quickAction?.action, quickActionAvatars]);

const navigateToQuickAction = () => {
const selectOption = (onSelected: () => void, shouldRestrictAction: boolean) => {
if (shouldRestrictAction && quickActionReport?.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(quickActionReport.policyID)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(quickActionReport.policyID));
return;
}

onSelected();
};

const isValidReport = !(isEmptyObject(quickActionReport) || ReportUtils.isArchivedRoom(quickActionReport));
const quickActionReportID = isValidReport ? quickActionReport?.reportID ?? '-1' : ReportUtils.generateReportID();

switch (quickAction?.action) {
case CONST.QUICK_ACTIONS.REQUEST_MANUAL:
IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true);
return;
case CONST.QUICK_ACTIONS.REQUEST_SCAN:
IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true), true);
return;
case CONST.QUICK_ACTIONS.REQUEST_DISTANCE:
IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SUBMIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), true);
return;
case CONST.QUICK_ACTIONS.SPLIT_MANUAL:
IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), true);
return;
case CONST.QUICK_ACTIONS.SPLIT_SCAN:
IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true), true);
return;
case CONST.QUICK_ACTIONS.SPLIT_DISTANCE:
IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.SPLIT, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), true);
return;
case CONST.QUICK_ACTIONS.SEND_MONEY:
IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.PAY, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), false);
return;
case CONST.QUICK_ACTIONS.ASSIGN_TASK:
Task.clearOutTaskInfoAndNavigate(isValidReport ? quickActionReportID : '', isValidReport ? quickActionReport : undefined, quickAction.targetAccountID ?? -1, true);
selectOption(
() => Task.clearOutTaskInfoAndNavigate(isValidReport ? quickActionReportID : '', isValidReport ? quickActionReport : undefined, quickAction.targetAccountID ?? -1, true),
false,
);
break;
case CONST.QUICK_ACTIONS.TRACK_MANUAL:
IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.MANUAL, true), false);
break;
case CONST.QUICK_ACTIONS.TRACK_SCAN:
IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.SCAN, true), false);
break;
case CONST.QUICK_ACTIONS.TRACK_DISTANCE:
IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true);
selectOption(() => IOU.startMoneyRequest(CONST.IOU.TYPE.TRACK, quickActionReportID, CONST.IOU.REQUEST_TYPE.DISTANCE, true), false);
break;
default:
}
Expand Down
15 changes: 12 additions & 3 deletions src/pages/iou/request/MoneyRequestParticipantsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ import usePermissions from '@hooks/usePermissions';
import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as SubscriptionUtils from '@libs/SubscriptionUtils';
import * as Policy from '@userActions/Policy/Policy';
import * as Report from '@userActions/Report';
import type {IOUAction, IOURequestType, IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Participant} from '@src/types/onyx/IOU';

type MoneyRequestParticipantsSelectorProps = {
Expand Down Expand Up @@ -380,12 +383,18 @@ function MoneyRequestParticipantsSelector({participants = CONST.EMPTY_ARRAY, onF
]);

const onSelectRow = useCallback(
(item: Participant) => {
(option: Participant) => {
if (option.isPolicyExpenseChat && option.policyID && SubscriptionUtils.shouldRestrictUserBillableActions(option.policyID)) {
Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(option.policyID));
return;
}

if (isIOUSplit) {
addParticipantToSelection(item);
addParticipantToSelection(option);
return;
}
addSingleParticipant(item);

addSingleParticipant(option);
},
[isIOUSplit, addParticipantToSelection, addSingleParticipant],
);
Expand Down
Loading
Loading