Skip to content

Commit

Permalink
Merge pull request #38381 from software-mansion-labs/travel/workspace…
Browse files Browse the repository at this point in the history
…-profile-address
  • Loading branch information
twisterdotcom authored May 1, 2024
2 parents 90435fe + f7da8c4 commit c8aa591
Show file tree
Hide file tree
Showing 15 changed files with 216 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ const CONST = {
TRACK_EXPENSE: 'trackExpense',
P2P_DISTANCE_REQUESTS: 'p2pDistanceRequests',
WORKFLOWS_DELAYED_SUBMISSION: 'workflowsDelayedSubmission',
SPOTNANA_TRAVEL: 'spotnanaTravel',
ACCOUNTING_ON_NEW_EXPENSIFY: 'accountingOnNewExpensify',
},
BUTTON_STATES: {
Expand Down
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/profile',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile` as const,
},
WORKSPACE_PROFILE_ADDRESS: {
route: 'settings/workspaces/:policyID/profile/address',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile/address` as const,
},
WORKSPACE_ACCOUNTING: {
route: 'settings/workspaces/:policyID/accounting',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting` as const,
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ const SCREENS = {
TAG_CREATE: 'Tag_Create',
TAG_SETTINGS: 'Tag_Settings',
CURRENCY: 'Workspace_Profile_Currency',
ADDRESS: 'Workspace_Profile_Address',
WORKFLOWS: 'Workspace_Workflows',
WORKFLOWS_PAYER: 'Workspace_Workflows_Payer',
WORKFLOWS_APPROVER: 'Workspace_Workflows_Approver',
Expand Down
12 changes: 12 additions & 0 deletions src/libs/API/parameters/UpdatePolicyAddressParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// TODO: Change API endpoint parameters format to make it possible to follow naming-convention
/* eslint-disable @typescript-eslint/naming-convention */
type UpdatePolicyAddressParams = {
policyID: string;
'data[addressStreet]': string;
'data[city]': string;
'data[country]': string;
'data[state]': string;
'data[zipCode]': string;
};

export default UpdatePolicyAddressParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export type {default as UpdateFrequentlyUsedEmojisParams} from './UpdateFrequent
export type {default as UpdateGroupChatNameParams} from './UpdateGroupChatNameParams';
export type {default as UpdateGroupChatMemberRolesParams} from './UpdateGroupChatMemberRolesParams';
export type {default as UpdateHomeAddressParams} from './UpdateHomeAddressParams';
export type {default as UpdatePolicyAddressParams} from './UpdatePolicyAddressParams';
export type {default as UpdateLegalNameParams} from './UpdateLegalNameParams';
export type {default as UpdateNewsletterSubscriptionParams} from './UpdateNewsletterSubscriptionParams';
export type {default as UpdatePersonalInformationForBankAccountParams} from './UpdatePersonalInformationForBankAccountParams';
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 @@ -42,6 +42,7 @@ const WRITE_COMMANDS = {
UPDATE_LEGAL_NAME: 'UpdateLegalName',
UPDATE_DATE_OF_BIRTH: 'UpdateDateOfBirth',
UPDATE_HOME_ADDRESS: 'UpdateHomeAddress',
UPDATE_POLICY_ADDRESS: 'SetPolicyAddress',
UPDATE_AUTOMATIC_TIMEZONE: 'UpdateAutomaticTimezone',
UPDATE_SELECTED_TIMEZONE: 'UpdateSelectedTimezone',
UPDATE_USER_AVATAR: 'UpdateUserAvatar',
Expand Down Expand Up @@ -243,6 +244,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UPDATE_DISPLAY_NAME]: Parameters.UpdateDisplayNameParams;
[WRITE_COMMANDS.UPDATE_LEGAL_NAME]: Parameters.UpdateLegalNameParams;
[WRITE_COMMANDS.UPDATE_DATE_OF_BIRTH]: Parameters.UpdateDateOfBirthParams;
[WRITE_COMMANDS.UPDATE_POLICY_ADDRESS]: Parameters.UpdatePolicyAddressParams;
[WRITE_COMMANDS.UPDATE_HOME_ADDRESS]: Parameters.UpdateHomeAddressParams;
[WRITE_COMMANDS.UPDATE_AUTOMATIC_TIMEZONE]: Parameters.UpdateAutomaticTimezoneParams;
[WRITE_COMMANDS.UPDATE_SELECTED_TIMEZONE]: Parameters.UpdateSelectedTimezoneParams;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
[SCREENS.WORKSPACE.SHARE]: () => require('../../../../pages/workspace/WorkspaceProfileSharePage').default as React.ComponentType,
[SCREENS.WORKSPACE.CURRENCY]: () => require('../../../../pages/workspace/WorkspaceProfileCurrencyPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORY_SETTINGS]: () => require('../../../../pages/workspace/categories/CategorySettingsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.ADDRESS]: () => require('../../../../pages/workspace/WorkspaceProfileAddressPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: () => require('../../../../pages/workspace/categories/WorkspaceCategoriesSettingsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.MEMBER_DETAILS]: () => require('../../../../pages/workspace/members/WorkspaceMemberDetailsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.OWNER_CHANGE_CHECK]: () => require('@pages/workspace/members/WorkspaceOwnerChangeWrapperPage').default as React.ComponentType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {FullScreenName} from '@libs/Navigation/types';
import SCREENS from '@src/SCREENS';

const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
[SCREENS.WORKSPACE.PROFILE]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.DESCRIPTION, SCREENS.WORKSPACE.SHARE],
[SCREENS.WORKSPACE.PROFILE]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.ADDRESS, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.DESCRIPTION, SCREENS.WORKSPACE.SHARE],
[SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT, SCREENS.WORKSPACE.RATE_AND_UNIT_RATE, SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT],
[SCREENS.WORKSPACE.MEMBERS]: [
SCREENS.WORKSPACE.INVITE,
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.CURRENCY]: {
path: ROUTES.WORKSPACE_PROFILE_CURRENCY.route,
},
[SCREENS.WORKSPACE.ADDRESS]: {
path: ROUTES.WORKSPACE_PROFILE_ADDRESS.route,
},
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_IMPORT]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_IMPORT.route},
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CHART_OF_ACCOUNTS.route},
[SCREENS.WORKSPACE.ACCOUNTING.QUICKBOOKS_ONLINE_CLASSES]: {path: ROUTES.POLICY_ACCOUNTING_QUICKBOOKS_ONLINE_CLASSES.route},
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ type SettingsNavigatorParamList = {
[SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE]: undefined;
[SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME]: undefined;
[SCREENS.WORKSPACE.CURRENCY]: undefined;
[SCREENS.WORKSPACE.ADDRESS]: undefined;
[SCREENS.WORKSPACE.NAME]: undefined;
[SCREENS.WORKSPACE.DESCRIPTION]: undefined;
[SCREENS.WORKSPACE.SHARE]: undefined;
Expand Down
5 changes: 5 additions & 0 deletions src/libs/Permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ function canUseWorkflowsDelayedSubmission(betas: OnyxEntry<Beta[]>): boolean {
return !!betas?.includes(CONST.BETAS.WORKFLOWS_DELAYED_SUBMISSION) || canUseAllBetas(betas);
}

function canUseSpotnanaTravel(betas: OnyxEntry<Beta[]>): boolean {
return !!betas?.includes(CONST.BETAS.SPOTNANA_TRAVEL) || canUseAllBetas(betas);
}

function canUseAccountingIntegrations(betas: OnyxEntry<Beta[]>): boolean {
return !!betas?.includes(CONST.BETAS.ACCOUNTING_ON_NEW_EXPENSIFY) || canUseAllBetas(betas);
}
Expand All @@ -56,5 +60,6 @@ export default {
canUseReportFields,
canUseP2PDistanceRequests,
canUseWorkflowsDelayedSubmission,
canUseSpotnanaTravel,
canUseAccountingIntegrations,
};
35 changes: 34 additions & 1 deletion src/libs/actions/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import type {
UpdateWorkspaceGeneralSettingsParams,
UpdateWorkspaceMembersRoleParams,
} from '@libs/API/parameters';
import type UpdatePolicyAddressParams from '@libs/API/parameters/UpdatePolicyAddressParams';
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import DateUtils from '@libs/DateUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
Expand Down Expand Up @@ -94,7 +95,7 @@ import type {
} from '@src/types/onyx';
import type {ErrorFields, Errors, OnyxValueWithOfflineFeedback, PendingAction} from '@src/types/onyx/OnyxCommon';
import type {OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage';
import type {Attributes, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy';
import type {Attributes, CompanyAddress, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy';
import type {OnyxData} from '@src/types/onyx/Request';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
Expand Down Expand Up @@ -1830,6 +1831,37 @@ function hideWorkspaceAlertMessage(policyID: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {alertMessage: ''});
}

function updateAddress(policyID: string, newAddress: CompanyAddress) {
// TODO: Change API endpoint parameters format to make it possible to follow naming-convention
const parameters: UpdatePolicyAddressParams = {
policyID,
// eslint-disable-next-line @typescript-eslint/naming-convention
'data[addressStreet]': newAddress.addressStreet,
// eslint-disable-next-line @typescript-eslint/naming-convention
'data[city]': newAddress.city,
// eslint-disable-next-line @typescript-eslint/naming-convention
'data[country]': newAddress.country,
// eslint-disable-next-line @typescript-eslint/naming-convention
'data[state]': newAddress.state,
// eslint-disable-next-line @typescript-eslint/naming-convention
'data[zipCode]': newAddress.zipCode,
};

const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
value: {
address: newAddress,
},
},
];

API.write(WRITE_COMMANDS.UPDATE_POLICY_ADDRESS, parameters, {
optimisticData,
});
}

function updateWorkspaceCustomUnitAndRate(policyID: string, currentCustomUnit: CustomUnit, newCustomUnit: NewCustomUnit, lastModified?: string) {
if (!currentCustomUnit.customUnitID || !newCustomUnit?.customUnitID || !newCustomUnit.rates?.customUnitRateID) {
return;
Expand Down Expand Up @@ -5044,6 +5076,7 @@ export {
clearCustomUnitErrors,
hideWorkspaceAlertMessage,
deleteWorkspace,
updateAddress,
updateWorkspaceCustomUnitAndRate,
updateLastAccessedWorkspace,
clearDeleteMemberError,
Expand Down
112 changes: 112 additions & 0 deletions src/pages/workspace/WorkspaceProfileAddressPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import AddressForm from '@components/AddressForm';
import type {FormOnyxValues} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {updateAddress} from '@userActions/Policy';
import type {Country} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import type {CompanyAddress} from '@src/types/onyx/Policy';
import type {WithPolicyProps} from './withPolicy';
import withPolicy from './withPolicy';

type WorkspaceProfileAddressPagePolicyProps = WithPolicyProps;

type WorkspaceProfileAddressPageProps = StackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.ADDRESS> & WorkspaceProfileAddressPagePolicyProps;

function WorkspaceProfileAddressPage({policy}: WorkspaceProfileAddressPageProps) {
const {translate} = useLocalize();
const address = useMemo(() => policy?.address, [policy]);
const [currentCountry, setCurrentCountry] = useState(address?.country);
const [[street1, street2], setStreets] = useState((address?.addressStreet ?? '').split('\n'));
const [state, setState] = useState(address?.state);
const [city, setCity] = useState(address?.city);
const [zipcode, setZipcode] = useState(address?.zipCode);

const updatePolicyAddress = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.HOME_ADDRESS_FORM>) => {
if (!policy) {
return;
}
updateAddress(policy?.id, {
addressStreet: `${values.addressLine1?.trim() ?? ''}\n${values.addressLine2?.trim() ?? ''}`,
city: values.city.trim(),
state: values.state.trim(),
zipCode: values?.zipPostCode?.trim().toUpperCase() ?? '',
country: values.country,
});
Navigation.goBack();
};

const handleAddressChange = useCallback((value: unknown, key: unknown) => {
const countryValue = value as Country | '';
const addressKey = key as keyof CompanyAddress;

if (addressKey !== 'country' && addressKey !== 'state' && addressKey !== 'city' && addressKey !== 'zipCode') {
return;
}
if (addressKey === 'country') {
setCurrentCountry(countryValue);
setState('');
setCity('');
setZipcode('');
return;
}
if (addressKey === 'state') {
setState(countryValue);
setCity('');
setZipcode('');
return;
}
if (addressKey === 'city') {
setCity(countryValue);
setZipcode('');
return;
}
setZipcode(countryValue);
}, []);

useEffect(() => {
if (!address) {
return;
}
setStreets((address?.addressStreet ?? '').split('\n'));
setState(address.state);
setCurrentCountry(address.country);
setCity(address.city);
setZipcode(address.zipCode);
}, [address]);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={WorkspaceProfileAddressPage.displayName}
>
<HeaderWithBackButton
title={translate('common.companyAddress')}
shouldShowBackButton
onBackButtonPress={() => Navigation.goBack()}
/>
<AddressForm
formID={ONYXKEYS.FORMS.HOME_ADDRESS_FORM}
onSubmit={updatePolicyAddress}
submitButtonText={translate('common.save')}
city={city}
country={currentCountry}
onAddressChanged={handleAddressChange}
state={state}
street1={street1}
street2={street2}
zip={zipcode}
/>
</ScreenWrapper>
);
}

WorkspaceProfileAddressPage.displayName = 'WorkspaceProfileAddressPage';

export default withPolicy(WorkspaceProfileAddressPage);
25 changes: 25 additions & 0 deletions src/pages/workspace/WorkspaceProfilePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Section from '@components/Section';
import Text from '@components/Text';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useLocalize from '@hooks/useLocalize';
import usePermissions from '@hooks/usePermissions';
import useThemeIllustrations from '@hooks/useThemeIllustrations';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
Expand Down Expand Up @@ -50,12 +51,20 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi
const {isSmallScreenWidth} = useWindowDimensions();
const illustrations = useThemeIllustrations();
const {activeWorkspaceID, setActiveWorkspaceID} = useActiveWorkspace();
const {canUseSpotnanaTravel} = usePermissions();

const outputCurrency = policy?.outputCurrency ?? '';
const currencySymbol = currencyList?.[outputCurrency]?.symbol ?? '';
const formattedCurrency = !isEmptyObject(policy) && !isEmptyObject(currencyList) ? `${outputCurrency} - ${currencySymbol}` : '';

const [street1, street2] = (policy?.address?.addressStreet ?? '').split('\n');
const formattedAddress =
!isEmptyObject(policy) && !isEmptyObject(policy.address)
? `${street1?.trim()}, ${street2 ? `${street2.trim()}, ` : ''}${policy.address.city}, ${policy.address.state} ${policy.address.zipCode ?? ''}`
: '';

const onPressCurrency = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_CURRENCY.getRoute(policy?.id ?? '')), [policy?.id]);
const onPressAddress = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(policy?.id ?? '')), [policy?.id]);
const onPressName = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_NAME.getRoute(policy?.id ?? '')), [policy?.id]);
const onPressDescription = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_DESCRIPTION.getRoute(policy?.id ?? '')), [policy?.id]);
const onPressShare = useCallback(() => Navigation.navigate(ROUTES.WORKSPACE_PROFILE_SHARE.getRoute(policy?.id ?? '')), [policy?.id]);
Expand Down Expand Up @@ -212,6 +221,22 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi
</Text>
</View>
</OfflineWithFeedback>
{canUseSpotnanaTravel && (
<OfflineWithFeedback pendingAction={policy?.pendingFields?.generalSettings}>
<View>
<MenuItemWithTopDescription
title={formattedAddress}
description={translate('common.companyAddress')}
shouldShowRightIcon={!readOnly}
disabled={readOnly}
wrapperStyle={styles.sectionMenuItemTopDescription}
onPress={onPressAddress}
shouldGreyOutWhenDisabled={false}
shouldUseDefaultCursorWhenDisabled
/>
</View>
</OfflineWithFeedback>
)}
{!readOnly && (
<View style={[styles.flexRow, styles.mt6, styles.mnw120]}>
<Button
Expand Down
13 changes: 13 additions & 0 deletions src/types/onyx/Policy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {ValueOf} from 'type-fest';
import type CONST from '@src/CONST';
import type {Country} from '@src/CONST';
import type * as OnyxTypes from '.';
import type * as OnyxCommon from './OnyxCommon';

Expand Down Expand Up @@ -30,6 +31,14 @@ type CustomUnit = OnyxCommon.OnyxValueWithOfflineFeedback<{
errorFields?: OnyxCommon.ErrorFields;
}>;

type CompanyAddress = {
addressStreet: string;
city: string;
state: string;
zipCode: string;
country: Country | '';
};

type DisabledFields = {
defaultBillable?: boolean;
reimbursable?: boolean;
Expand Down Expand Up @@ -364,6 +373,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback<
/** The output currency for the policy */
outputCurrency: string;

/** The address of the company */
address?: CompanyAddress;

/** The URL for the policy avatar */
avatar?: string;
avatarURL?: string;
Expand Down Expand Up @@ -536,6 +548,7 @@ export type {
TaxRate,
TaxRates,
TaxRatesWithDefault,
CompanyAddress,
IntegrationEntityMap,
PolicyFeatureName,
PendingJoinRequestPolicy,
Expand Down

0 comments on commit c8aa591

Please sign in to comment.