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] Implement Restricted Action screen #43855

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/taxes',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/taxes` as const,
},
RESTRICTED_ACTION: {
route: 'restricted-action/workspace/:policyID',
getRoute: (policyID: string) => `restricted-action/workspace/${policyID}` as const,
},
} as const;

/**
Expand Down
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const SCREENS = {
TRAVEL: 'Travel',
SEARCH_REPORT: 'SearchReport',
SETTINGS_CATEGORIES: 'SettingsCategories',
RESTRICTED_ACTION: 'RestrictedAction',
},
ONBOARDING_MODAL: {
ONBOARDING: 'Onboarding',
Expand Down Expand Up @@ -378,6 +379,7 @@ const SCREENS = {
KEYBOARD_SHORTCUTS: 'KeyboardShortcuts',
TRANSACTION_RECEIPT: 'TransactionReceipt',
FEATURE_TRAINING_ROOT: 'FeatureTraining_Root',
RESTRICTED_ACTION_ROOT: 'RestrictedAction_Root',
} as const;

type Screen = DeepValueOf<typeof SCREENS>;
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Illustrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import House from '@assets/images/simple-illustrations/simple-illustration__hous
import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg';
import Lightbulb from '@assets/images/simple-illustrations/simple-illustration__lightbulb.svg';
import LockClosed from '@assets/images/simple-illustrations/simple-illustration__lockclosed.svg';
import LockClosedOrange from '@assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg';
import LockOpen from '@assets/images/simple-illustrations/simple-illustration__lockopen.svg';
import Luggage from '@assets/images/simple-illustrations/simple-illustration__luggage.svg';
import Mailbox from '@assets/images/simple-illustrations/simple-illustration__mailbox.svg';
Expand Down Expand Up @@ -192,4 +193,5 @@ export {
SendMoney,
CheckmarkCircle,
CreditCardEyes,
LockClosedOrange,
};
13 changes: 13 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2689,6 +2689,19 @@ export default {
errorDescriptionPartTwo: 'reach out to Concierge',
errorDescriptionPartThree: 'for help.',
},
restrictedAction: {
restricted: 'Restricted',
actionsAreCurrentlyRestricted: ({workspaceName}) => `Actions on the ${workspaceName} workspace are currently restricted`,
workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}) =>
`Workspace owner, ${workspaceOwnerName} will need to add or update the payment card on file to unlock new workspace activity.`,
youWillNeedToAddOrUpdatePaymentCard: "You'll need to add or update the payment card on file to unlock new workspace activity.",
addPaymentCardToUnlock: 'Add a payment card to unlock!',
addPaymentCardToContinueUsingWorkspace: 'Add a payment card to continue using this workspace',
pleaseReachOutToYourWorkspaceAdmin: 'Please reach out to your workspace admin for any questions.',
chatWithYourAdmin: 'Chat with your admin',
chatInAdmins: 'Chat in #admins',
addPaymentCard: 'Add payment card',
},
},
getAssistancePage: {
title: 'Get assistance',
Expand Down
13 changes: 13 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2727,6 +2727,19 @@ export default {
errorDescriptionPartTwo: 'contacta con el conserje',
errorDescriptionPartThree: 'por ayuda.',
},
restrictedAction: {
restricted: 'Restringido',
actionsAreCurrentlyRestricted: ({workspaceName}) => `Las acciones en el espacio de trabajo ${workspaceName} están actualmente restringidas`,
workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}) =>
`El propietario del espacio de trabajo, ${workspaceOwnerName} tendrá que añadir o actualizar la tarjeta de pago registrada para desbloquear nueva actividad en el espacio de trabajo.`,
youWillNeedToAddOrUpdatePaymentCard: 'Debes añadir o actualizar la tarjeta de pago registrada para desbloquear nueva actividad en el espacio de trabajo.',
addPaymentCardToUnlock: 'Añade una tarjeta para desbloquearlo!',
addPaymentCardToContinueUsingWorkspace: 'Añade una tarjeta de pago para seguir utilizando este espacio de trabajo',
pleaseReachOutToYourWorkspaceAdmin: 'Si tienes alguna pregunta, ponte en contacto con el administrador de su espacio de trabajo.',
chatWithYourAdmin: 'Chatea con tu administrador',
chatInAdmins: 'Chatea en #admins',
addPaymentCard: 'Agregar tarjeta de pago',
},
},
getAssistancePage: {
title: 'Obtener ayuda',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,10 @@ const SearchReportModalStackNavigator = createModalStackNavigator<SearchReportPa
[SCREENS.SEARCH.REPORT_RHP]: () => require('../../../../pages/home/ReportScreen').default as React.ComponentType,
});

const RestrictedActionModalStackNavigator = createModalStackNavigator<SearchReportParamList>({
[SCREENS.RESTRICTED_ACTION_ROOT]: () => require('../../../../pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage').default as React.ComponentType,
});

export {
AddPersonalBankAccountModalStackNavigator,
EditRequestStackNavigator,
Expand Down Expand Up @@ -400,4 +404,5 @@ export {
TaskModalStackNavigator,
WalletStatementStackNavigator,
SearchReportModalStackNavigator,
RestrictedActionModalStackNavigator,
};
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) {
name={SCREENS.RIGHT_MODAL.SEARCH_REPORT}
component={ModalStackNavigators.SearchReportModalStackNavigator}
/>
<Stack.Screen
name={SCREENS.RIGHT_MODAL.RESTRICTED_ACTION}
component={ModalStackNavigators.RestrictedActionModalStackNavigator}
/>
</Stack.Navigator>
</View>
</NoDropZone>
Expand Down
5 changes: 5 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,11 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT.route,
},
},
[SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: {
screens: {
[SCREENS.RESTRICTED_ACTION_ROOT]: ROUTES.RESTRICTED_ACTION.route,
},
},
},
},

Expand Down
8 changes: 8 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ type RightModalNavigatorParamList = {
[SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: NavigatorScreenParams<PrivateNotesNavigatorParamList>;
[SCREENS.RIGHT_MODAL.TRAVEL]: NavigatorScreenParams<TravelNavigatorParamList>;
[SCREENS.RIGHT_MODAL.SEARCH_REPORT]: NavigatorScreenParams<SearchReportParamList>;
[SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: NavigatorScreenParams<RestrictedActionParamList>;
};

type TravelNavigatorParamList = {
Expand Down Expand Up @@ -943,6 +944,12 @@ type SearchReportParamList = {
};
};

type RestrictedActionParamList = {
[SCREENS.RESTRICTED_ACTION_ROOT]: {
policyID: string;
};
};

type RootStackParamList = PublicScreensParamList & AuthScreensParamList & LeftModalNavigatorParamList;

type BottomTabName = keyof BottomTabNavigatorParamList;
Expand Down Expand Up @@ -1007,4 +1014,5 @@ export type {
WalletStatementNavigatorParamList,
WelcomeVideoModalNavigatorParamList,
SearchReportParamList,
RestrictedActionParamList,
};
7 changes: 7 additions & 0 deletions src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ function isExpensifyTeam(email: string | undefined): boolean {
const isPolicyAdmin = (policy: OnyxInputOrEntry<Policy> | EmptyObject, currentUserLogin?: string): boolean =>
(policy?.role ?? (currentUserLogin && policy?.employeeList?.[currentUserLogin]?.role)) === CONST.POLICY.ROLE.ADMIN;

/**
* Checks if the current user is an user of the policy.
Copy link
Contributor

Choose a reason for hiding this comment

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

I find this weird to read current user is an user. Maybe making it more verbose like so would be easier to read,

Suggested change
* Checks if the current user is an user of the policy.
* Checks if the current user is of the role "user" on the policy.

Copy link
Contributor Author

@pac-guerreiro pac-guerreiro Jun 25, 2024

Choose a reason for hiding this comment

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

*/
const isPolicyUser = (policy: OnyxInputOrEntry<Policy> | EmptyObject, currentUserLogin?: string): boolean =>
(policy?.role ?? (currentUserLogin && policy?.employeeList?.[currentUserLogin]?.role)) === CONST.POLICY.ROLE.USER;

/**
* Checks if the policy is a free group policy.
*/
Expand Down Expand Up @@ -533,6 +539,7 @@ export {
isPaidGroupPolicy,
isPendingDeletePolicy,
isPolicyAdmin,
isPolicyUser,
isPolicyEmployee,
isPolicyFeatureEnabled,
isPolicyOwner,
Expand Down
5 changes: 5 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6984,6 +6984,10 @@ function getChatUsedForOnboarding(): OnyxEntry<Report> {
return Object.values(allReports ?? {}).find(isChatUsedForOnboarding);
}

function findPolicyExpenseChatByPolicyID(policyID: string): OnyxEntry<Report> {
return Object.values(allReports ?? {}).find((report) => isPolicyExpenseChat(report) && report?.policyID === policyID);
}

pac-guerreiro marked this conversation as resolved.
Show resolved Hide resolved
export {
addDomainToShortMention,
areAllRequestsBeingSmartScanned,
Expand Down Expand Up @@ -7258,6 +7262,7 @@ export {
createDraftWorkspaceAndNavigateToConfirmationScreen,
isChatUsedForOnboarding,
getChatUsedForOnboarding,
findPolicyExpenseChatByPolicyID,
};

export type {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, {useCallback} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Illustrations from '@components/Icon/Illustrations';
import ImageSVG from '@components/ImageSVG';
import ScreenWrapper from '@components/ScreenWrapper';
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';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

type WorkspaceAdminRestrictedActionProps = {
policyID: string;
};

function WorkspaceAdminRestrictedAction({policyID}: WorkspaceAdminRestrictedActionProps) {
const {translate} = useLocalize();
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
const styles = useThemeStyles();

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

return (
<ScreenWrapper
includeSafeAreaPaddingBottom
testID={WorkspaceAdminRestrictedAction.displayName}
>
<HeaderWithBackButton
title={translate('workspace.restrictedAction.restricted')}
onBackButtonPress={Navigation.goBack}
/>
<ScrollView
style={[styles.p5, styles.pt0]}
contentContainerStyle={styles.flexGrow1}
>
<View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter, styles.mb15]}>
<ImageSVG
src={Illustrations.LockClosedOrange}
width={variables.restrictedActionIllustrationHeight}
height={variables.restrictedActionIllustrationHeight}
/>
<Text style={[styles.textHeadlineH1, styles.textAlignCenter]}>
{translate('workspace.restrictedAction.actionsAreCurrentlyRestricted', {workspaceName: policy?.name})}
</Text>
<Text style={[styles.textLabelSupportingEmptyValue, styles.textAlignCenter, styles.lh20, styles.mt2]}>
{translate('workspace.restrictedAction.workspaceOwnerWillNeedToAddOrUpdatePaymentCard', {workspaceOwnerName: policy?.owner})}
</Text>
</View>
<Button
text={translate('workspace.restrictedAction.chatInAdmins')}
onPress={openAdminsReport}
success
large
/>
</ScrollView>
</ScreenWrapper>
);
}

WorkspaceAdminRestrictedAction.displayName = 'WorkspaceAdminRestrictedAction';

export default WorkspaceAdminRestrictedAction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import {View} from 'react-native';
import Badge from '@components/Badge';
import Button from '@components/Button';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import ROUTES from '@src/ROUTES';

function WorkspaceOwnerRestrictedAction() {
const {translate} = useLocalize();
const styles = useThemeStyles();

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

return (
<ScreenWrapper
includeSafeAreaPaddingBottom
testID={WorkspaceOwnerRestrictedAction.displayName}
>
<HeaderWithBackButton
title={translate('workspace.restrictedAction.restricted')}
onBackButtonPress={Navigation.goBack}
/>
<ScrollView contentContainerStyle={[styles.ph5, styles.pt3]}>
<View style={[styles.cardSectionContainer, styles.p5, styles.mb0, styles.mh0]}>
<View style={[styles.flexRow, styles.justifyContentBetween, styles.alignItemsStart, styles.mb3]}>
<Icon
src={Illustrations.LockClosedOrange}
height={variables.iconHeader}
width={variables.iconHeader}
/>
<Badge
icon={Expensicons.Unlock}
success
text={translate('workspace.restrictedAction.addPaymentCardToUnlock')}
badgeStyles={styles.alignSelfStart}
/>
</View>
<Text style={[styles.textHeadlineH1, styles.mb4]}>{translate('workspace.restrictedAction.addPaymentCardToContinueUsingWorkspace')}</Text>
<Text style={[styles.textLabelSupportingEmptyValue, styles.mb5]}>{translate('workspace.restrictedAction.youWillNeedToAddOrUpdatePaymentCard')}</Text>
<Button
pac-guerreiro marked this conversation as resolved.
Show resolved Hide resolved
text={translate('workspace.restrictedAction.addPaymentCard')}
onPress={addPaymentCard}
success
large
/>
</View>
</ScrollView>
</ScreenWrapper>
);
}

WorkspaceOwnerRestrictedAction.displayName = 'WorkspaceOwnerRestrictedAction';

export default WorkspaceOwnerRestrictedAction;
Loading
Loading