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

feat: Provide education/confirmation before creating workspaces in New Workspace flows #52164

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ const ONYXKEYS = {
ADD_PAYMENT_CARD_FORM_DRAFT: 'addPaymentCardFormDraft',
WORKSPACE_SETTINGS_FORM: 'workspaceSettingsForm',
WORKSPACE_CATEGORY_FORM: 'workspaceCategoryForm',
WORKSPACE_CONFIRMATION_FORM: 'workspaceConfirmationForm',
WORKSPACE_CONFIRMATION_FORM_DRAFT: 'workspaceConfirmationFormDraft',
WORKSPACE_CATEGORY_FORM_DRAFT: 'workspaceCategoryFormDraft',
WORKSPACE_CATEGORY_DESCRIPTION_HINT_FORM: 'workspaceCategoryDescriptionHintForm',
WORKSPACE_CATEGORY_DESCRIPTION_HINT_FORM_DRAFT: 'workspaceCategoryDescriptionHintFormDraft',
Expand Down Expand Up @@ -728,6 +730,7 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.ADD_PAYMENT_CARD_FORM]: FormTypes.AddPaymentCardForm;
[ONYXKEYS.FORMS.WORKSPACE_SETTINGS_FORM]: FormTypes.WorkspaceSettingsForm;
[ONYXKEYS.FORMS.WORKSPACE_CATEGORY_FORM]: FormTypes.WorkspaceCategoryForm;
[ONYXKEYS.FORMS.WORKSPACE_CONFIRMATION_FORM]: FormTypes.WorkspaceConfirmationForm;
[ONYXKEYS.FORMS.WORKSPACE_TAG_FORM]: FormTypes.WorkspaceTagForm;
[ONYXKEYS.FORMS.WORKSPACE_TAX_CUSTOM_NAME]: FormTypes.WorkspaceTaxCustomName;
[ONYXKEYS.FORMS.WORKSPACE_COMPANY_CARD_FEED_NAME]: FormTypes.WorkspaceCompanyCardFeedName;
Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,7 @@ const ROUTES = {
},
WELCOME_VIDEO_ROOT: 'onboarding/welcome-video',
EXPLANATION_MODAL_ROOT: 'onboarding/explanation',
WORKSPACE_CONFIRMATION: 'workspace/confirmation',

TRANSACTION_RECEIPT: {
route: 'r/:reportID/transaction/:transactionID/receipt',
Expand Down
3 changes: 3 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ const SCREENS = {
DETAILS: 'Details',
PROFILE: 'Profile',
REPORT_DETAILS: 'Report_Details',
WORKSPACE_CONFIRMATION: 'Workspace_Confirmation',
REPORT_SETTINGS: 'Report_Settings',
REPORT_DESCRIPTION: 'Report_Description',
PARTICIPANTS: 'Participants',
Expand Down Expand Up @@ -310,6 +311,8 @@ const SCREENS = {
EXPORT: 'Report_Details_Export',
},

WORKSPACE_CONFIRMATION: {ROOT: 'Workspace_Confirmation_Root'},

WORKSPACE: {
ACCOUNTING: {
ROOT: 'Policy_Accounting',
Expand Down
87 changes: 87 additions & 0 deletions src/components/CurrencyPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, {forwardRef, useState} from 'react';
import type {ForwardedRef} from 'react';
import {View} from 'react-native';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import CurrencySelectionListWithOnyx from './CurrencySelectionList';
import HeaderWithBackButton from './HeaderWithBackButton';
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
import Modal from './Modal';
import ScreenWrapper from './ScreenWrapper';
import type {ValuePickerItem, ValuePickerProps} from './ValuePicker/types';

type CurrencyPickerProps = {
selectedCurrency?: string;
};
function CurrencyPicker({selectedCurrency, label, errorText = '', value, onInputChange, furtherDetails}: ValuePickerProps & CurrencyPickerProps, forwardedRef: ForwardedRef<View>) {
const StyleUtils = useStyleUtils();
const styles = useThemeStyles();
const [isPickerVisible, setIsPickerVisible] = useState(false);

const showPickerModal = () => {
setIsPickerVisible(true);
};

const hidePickerModal = () => {
setIsPickerVisible(false);
};

const updateInput = (item: ValuePickerItem) => {
if (item.value !== selectedCurrency) {
onInputChange?.(item.value);
}
hidePickerModal();
};

const descStyle = !selectedCurrency || selectedCurrency.length === 0 ? StyleUtils.getFontSizeStyle(variables.fontSizeLabel) : null;

return (
<View>
<MenuItemWithTopDescription
ref={forwardedRef}
shouldShowRightIcon
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
title={value || ''}
descriptionTextStyle={descStyle}
description={label}
onPress={showPickerModal}
furtherDetails={furtherDetails}
brickRoadIndicator={errorText ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
errorText={errorText}
/>

<Modal
type={CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
isVisible={isPickerVisible}
onClose={() => hidePickerModal}
onModalHide={hidePickerModal}
hideModalContentWhileAnimating
useNativeDriver
onBackdropPress={hidePickerModal}
>
<ScreenWrapper
style={styles.pb0}
includePaddingTop={false}
includeSafeAreaPaddingBottom={false}
testID={label ?? 'TEST'}
>
<HeaderWithBackButton
title={label}
onBackButtonPress={hidePickerModal}
/>
<CurrencySelectionListWithOnyx
onSelect={(item) => updateInput({value: item.currencyCode})}
searchInputLabel="Currency"
initiallySelectedCurrencyCode={selectedCurrency}
/>
</ScreenWrapper>
</Modal>
</View>
);
}

CurrencyPicker.displayName = 'CurrencyPicker';

export default forwardRef(CurrencyPicker);
7 changes: 7 additions & 0 deletions src/libs/CurrencyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import CONST from '@src/CONST';
import type {OnyxValues} from '@src/ONYXKEYS';
import ONYXKEYS from '@src/ONYXKEYS';
import {Currency} from '@src/types/onyx';

Check failure on line 5 in src/libs/CurrencyUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

All imports in the declaration are only used as types. Use `import type`

Check failure on line 5 in src/libs/CurrencyUtils.ts

View workflow job for this annotation

GitHub Actions / ESLint check

All imports in the declaration are only used as types. Use `import type`
import BaseLocaleListener from './Localize/LocaleListener/BaseLocaleListener';
import * as NumberFormatUtils from './NumberFormatUtils';

Expand Down Expand Up @@ -30,6 +31,11 @@
return decimals ?? 2;
}

function getCurrency(currency: string = CONST.CURRENCY.USD): Currency | null {
const currencyItem = currencyList?.[currency];
return currencyItem;
}

/**
* Returns the currency's minor unit quantity
* e.g. Cent in USD
Expand Down Expand Up @@ -211,4 +217,5 @@
convertToDisplayStringWithoutCurrency,
isValidCurrencyCode,
convertToShortDisplayString,
getCurrency,
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
TransactionDuplicateNavigatorParamList,
TravelNavigatorParamList,
WalletStatementNavigatorParamList,
WorkspaceConfirmationNavigatorParamList,
} from '@navigation/types';
import type {ThemeStyles} from '@styles/index';
import type {Screen} from '@src/SCREENS';
Expand Down Expand Up @@ -129,6 +130,10 @@ const ReportSettingsModalStackNavigator = createModalStackNavigator<ReportSettin
[SCREENS.REPORT_SETTINGS.VISIBILITY]: () => require<ReactComponentModule>('../../../../pages/settings/Report/VisibilityPage').default,
});

const WorkspaceConfirmationModalStackNavigator = createModalStackNavigator<WorkspaceConfirmationNavigatorParamList>({
[SCREENS.WORKSPACE_CONFIRMATION.ROOT]: () => require<ReactComponentModule>('../../../../pages/workspace/WorkspaceConfirmationPage').default,
});

const TaskModalStackNavigator = createModalStackNavigator<TaskDetailsNavigatorParamList>({
[SCREENS.TASK.TITLE]: () => require<ReactComponentModule>('../../../../pages/tasks/TaskTitlePage').default,
[SCREENS.TASK.ASSIGNEE]: () => require<ReactComponentModule>('../../../../pages/tasks/TaskAssigneeSelectorModal').default,
Expand Down Expand Up @@ -700,4 +705,5 @@ export {
SearchSavedSearchModalStackNavigator,
MissingPersonalDetailsModalStackNavigator,
DebugModalStackNavigator,
WorkspaceConfirmationModalStackNavigator,
};
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) {
name={SCREENS.RIGHT_MODAL.REPORT_DETAILS}
component={ModalStackNavigators.ReportDetailsModalStackNavigator}
/>

<Stack.Screen
name={SCREENS.RIGHT_MODAL.REPORT_SETTINGS}
component={ModalStackNavigators.ReportSettingsModalStackNavigator}
Expand Down Expand Up @@ -127,6 +128,10 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) {
name={SCREENS.RIGHT_MODAL.MONEY_REQUEST}
component={ModalStackNavigators.MoneyRequestModalStackNavigator}
/>
<Stack.Screen
name={SCREENS.RIGHT_MODAL.WORKSPACE_CONFIRMATION}
component={ModalStackNavigators.WorkspaceConfirmationModalStackNavigator}
/>
<Stack.Screen
name={SCREENS.RIGHT_MODAL.NEW_TASK}
component={ModalStackNavigators.NewTaskModalStackNavigator}
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 @@ -1122,6 +1122,11 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
},
},
},
[SCREENS.RIGHT_MODAL.WORKSPACE_CONFIRMATION]: {
screens: {
[SCREENS.WORKSPACE_CONFIRMATION.ROOT]: ROUTES.WORKSPACE_CONFIRMATION,
},
},
[SCREENS.RIGHT_MODAL.NEW_TASK]: {
screens: {
[SCREENS.NEW_TASK.ROOT]: ROUTES.NEW_TASK.route,
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,10 @@ type MoneyRequestNavigatorParamList = {
};
};

type WorkspaceConfirmationNavigatorParamList = {
[SCREENS.WORKSPACE_CONFIRMATION.ROOT]: undefined;
};

type NewTaskNavigatorParamList = {
[SCREENS.NEW_TASK.ROOT]: {
backTo?: Routes;
Expand Down Expand Up @@ -1350,6 +1354,7 @@ type RightModalNavigatorParamList = {
[SCREENS.RIGHT_MODAL.PARTICIPANTS]: NavigatorScreenParams<ParticipantsNavigatorParamList>;
[SCREENS.RIGHT_MODAL.ROOM_MEMBERS]: NavigatorScreenParams<RoomMembersNavigatorParamList>;
[SCREENS.RIGHT_MODAL.MONEY_REQUEST]: NavigatorScreenParams<MoneyRequestNavigatorParamList>;
[SCREENS.RIGHT_MODAL.WORKSPACE_CONFIRMATION]: NavigatorScreenParams<WorkspaceConfirmationNavigatorParamList>;
[SCREENS.RIGHT_MODAL.NEW_TASK]: NavigatorScreenParams<NewTaskNavigatorParamList>;
[SCREENS.RIGHT_MODAL.TEACHERS_UNITE]: NavigatorScreenParams<TeachersUniteNavigatorParamList>;
[SCREENS.RIGHT_MODAL.TASK_DETAILS]: NavigatorScreenParams<TaskDetailsNavigatorParamList>;
Expand Down Expand Up @@ -1704,4 +1709,5 @@ export type {
RestrictedActionParamList,
MissingPersonalDetailsParamList,
DebugParamList,
WorkspaceConfirmationNavigatorParamList,
};
23 changes: 17 additions & 6 deletions src/libs/actions/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,18 +366,29 @@ function endSignOnTransition() {
* @param [transitionFromOldDot] Optional, if the user is transitioning from old dot
* @param [makeMeAdmin] Optional, leave the calling account as an admin on the policy
* @param [backTo] An optional return path. If provided, it will be URL-encoded and appended to the resulting URL.
* @param [policyID] Optional, Policy id.
* @param [file],file
*/
function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', policyName = '', transitionFromOldDot = false, makeMeAdmin = false, backTo = '') {
const policyID = Policy.generatePolicyID();
Policy.createDraftInitialWorkspace(policyOwnerEmail, policyName, policyID, makeMeAdmin);
function createWorkspaceWithPolicyDraftAndNavigateToIt(
policyOwnerEmail = '',
policyName = '',
transitionFromOldDot = false,
makeMeAdmin = false,
backTo = '',
policyID = '',
currency?: string,
file?: File,
) {
const genereatedPolicyID = Policy.generatePolicyID();
Policy.createDraftInitialWorkspace(policyOwnerEmail, policyName, policyID || genereatedPolicyID, makeMeAdmin, currency, file);

Navigation.isNavigationReady()
.then(() => {
if (transitionFromOldDot) {
// We must call goBack() to remove the /transition route from history
Navigation.goBack();
}
savePolicyDraftByNewWorkspace(policyID, policyName, policyOwnerEmail, makeMeAdmin);
savePolicyDraftByNewWorkspace(policyID, policyName, policyOwnerEmail, makeMeAdmin, currency, file);
Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID, backTo));
})
.then(endSignOnTransition);
Expand All @@ -391,8 +402,8 @@ function createWorkspaceWithPolicyDraftAndNavigateToIt(policyOwnerEmail = '', po
* @param [policyOwnerEmail] Optional, the email of the account to make the owner of the policy
* @param [makeMeAdmin] Optional, leave the calling account as an admin on the policy
*/
function savePolicyDraftByNewWorkspace(policyID?: string, policyName?: string, policyOwnerEmail = '', makeMeAdmin = false) {
Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID);
function savePolicyDraftByNewWorkspace(policyID?: string, policyName?: string, policyOwnerEmail = '', makeMeAdmin = false, currency = '', file?: File) {
Policy.createWorkspace(policyOwnerEmail, makeMeAdmin, policyName, policyID, '', currency, file);
}

/**
Expand Down
37 changes: 29 additions & 8 deletions src/libs/actions/Policy/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1548,9 +1548,9 @@
* @param [policyID] custom policy id we will use for created workspace
* @param [makeMeAdmin] leave the calling account as an admin on the policy
*/
function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', policyID = generatePolicyID(), makeMeAdmin = false) {
function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', policyID = generatePolicyID(), makeMeAdmin = false, currency = '', file?: File) {
const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail);
const {customUnits, outputCurrency} = buildOptimisticCustomUnits();
const {customUnits, outputCurrency} = buildOptimisticCustomUnits(currency);

const optimisticData: OnyxUpdate[] = [
{
Expand All @@ -1565,12 +1565,14 @@
ownerAccountID: sessionAccountID,
isPolicyExpenseChatEnabled: true,
areCategoriesEnabled: true,
outputCurrency,
outputCurrency: currency || outputCurrency,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
customUnits,
makeMeAdmin,
autoReporting: true,
autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT,
avatarURL: file?.uri ?? null,
originalFileName: file?.name,
employeeList: {
[sessionEmail]: {
role: CONST.POLICY.ROLE.ADMIN,
Expand Down Expand Up @@ -1602,10 +1604,19 @@
* @param [policyID] custom policy id we will use for created workspace
* @param [expenseReportId] the reportID of the expense report that is being used to create the workspace
*/
function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), expenseReportId?: string, engagementChoice?: string) {
function buildPolicyData(
policyOwnerEmail = '',
makeMeAdmin = false,
policyName = '',
policyID = generatePolicyID(),
expenseReportId?: string,
engagementChoice?: string,
currency?: '',
file?: File,
) {
const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail);

const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticCustomUnits();
const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticCustomUnits(currency);

const {
adminsChatReportID,
Expand All @@ -1632,7 +1643,7 @@
owner: sessionEmail,
ownerAccountID: sessionAccountID,
isPolicyExpenseChatEnabled: true,
outputCurrency,
outputCurrency: currency || outputCurrency,

Check failure on line 1646 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator

Check failure on line 1646 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
autoReporting: true,
autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT,
Expand Down Expand Up @@ -1663,6 +1674,8 @@
address: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
description: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
avatarURL: file?.uri,
originalFileName: file?.name,
},
},
{
Expand Down Expand Up @@ -1834,8 +1847,16 @@
* @param [policyID] custom policy id we will use for created workspace
* @param [engagementChoice] Purpose of using application selected by user in guided setup flow
*/
function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), engagementChoice = ''): CreateWorkspaceParams {
const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID, undefined, engagementChoice);
function createWorkspace(
policyOwnerEmail = '',
makeMeAdmin = false,
policyName = '',
policyID = generatePolicyID(),
engagementChoice = '',
currency = '',
file?: File,
): CreateWorkspaceParams {
const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID, undefined, engagementChoice, currency, file);

Check failure on line 1859 in src/libs/actions/Policy/Policy.ts

View workflow job for this annotation

GitHub Actions / typecheck

Argument of type 'string' is not assignable to parameter of type '""'.
API.write(WRITE_COMMANDS.CREATE_WORKSPACE, params, {optimisticData, successData, failureData});

return params;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import * as Illustrations from '@components/Icon/Illustrations';
import Section, {CARD_LAYOUT} from '@components/Section';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as App from '@userActions/App';
import Navigation from '@libs/Navigation/Navigation';
import ROUTES from '@src/ROUTES';

function WorkspaceCardCreateAWorkspace() {
const styles = useThemeStyles();
Expand All @@ -21,7 +22,7 @@ function WorkspaceCardCreateAWorkspace() {
>
<Button
onPress={() => {
App.createWorkspaceWithPolicyDraftAndNavigateToIt();
Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION);
}}
text={translate('workspace.emptyWorkspace.createAWorkspaceCTA')}
style={styles.mt5}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import * as SubscriptionUtils from '@libs/SubscriptionUtils';
import {getNavatticURL} from '@libs/TourUtils';
import variables from '@styles/variables';
import * as App from '@userActions/App';

Check failure on line 34 in src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'App' is defined but never used

Check failure on line 34 in src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx

View workflow job for this annotation

GitHub Actions / ESLint check

'App' is defined but never used
import * as IOU from '@userActions/IOU';
import * as Link from '@userActions/Link';
import * as Policy from '@userActions/Policy/Policy';
Expand Down Expand Up @@ -549,7 +549,7 @@
iconHeight: variables.h40,
text: translate('workspace.new.newWorkspace'),
description: translate('workspace.new.getTheExpensifyCardAndMore'),
onSelected: () => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()),
onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION)),
},
]
: []),
Expand Down
Loading
Loading