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

Account switcher #47236

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a06431b
add delegate access type
rushatgabhane Aug 12, 2024
567058b
add auth token for delegate access
rushatgabhane Aug 12, 2024
40c55fb
add connect as delegate action
rushatgabhane Aug 12, 2024
62ee905
make delegated access optional
rushatgabhane Aug 12, 2024
3d31f79
rm display name and email
rushatgabhane Aug 12, 2024
90a0bc9
fix padding bottom style
rushatgabhane Aug 12, 2024
0a2b2f7
fix padding bottom style
rushatgabhane Aug 12, 2024
b9d2586
move profile photo to profile page
rushatgabhane Aug 12, 2024
2a4eaac
align left
rushatgabhane Aug 12, 2024
7e70fa3
cleanup
rushatgabhane Aug 12, 2024
1a5e6f7
move share button to profile page
rushatgabhane Aug 12, 2024
a6b9a44
add padding to share btn
rushatgabhane Aug 12, 2024
d3fbf69
create account switcher component
rushatgabhane Aug 12, 2024
28dd908
adjust styles to be responsive and text overflow
rushatgabhane Aug 13, 2024
c93b7c9
add caret up down icon
rushatgabhane Aug 13, 2024
d0cf30b
add caret up down icon
rushatgabhane Aug 13, 2024
6112f60
show account switcher
rushatgabhane Aug 13, 2024
d8d006d
add min width 0 style
rushatgabhane Aug 13, 2024
cebf129
add beta
rushatgabhane Aug 13, 2024
3655383
rm unnecessary style
rushatgabhane Aug 13, 2024
57dfbed
add popover menu to show delegates
rushatgabhane Aug 13, 2024
190a319
cleanup
rushatgabhane Aug 13, 2024
110af74
add styling for menu item
rushatgabhane Aug 13, 2024
890b23e
add badge text
rushatgabhane Aug 13, 2024
d15ffab
add lang
rushatgabhane Aug 13, 2024
e0aaba5
add width for account switcher popover
rushatgabhane Aug 13, 2024
e05b5d9
cleanup
rushatgabhane Aug 13, 2024
77f255b
cleanup
rushatgabhane Aug 13, 2024
cc3c722
cleanup
rushatgabhane Aug 13, 2024
b298b0b
add menu item type
rushatgabhane Aug 13, 2024
f426c00
add todo for error handling
rushatgabhane Aug 13, 2024
4f64b1d
correct border color
rushatgabhane Aug 13, 2024
9481177
correct border color
rushatgabhane Aug 13, 2024
d85b8af
add api call to connect
rushatgabhane Aug 13, 2024
8af74df
fix conflict
rushatgabhane Aug 13, 2024
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
17 changes: 17 additions & 0 deletions assets/images/caret-up-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ const CONST = {
REPORT_FIELDS_FEATURE: 'reportFieldsFeature',
WORKSPACE_FEEDS: 'workspaceFeeds',
NETSUITE_USA_TAX: 'netsuiteUsaTax',
NEW_DOT_COPILOT: 'newDotCopilot',
},
BUTTON_STATES: {
DEFAULT: 'default',
Expand Down
146 changes: 146 additions & 0 deletions src/components/AccountSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import {Str} from 'expensify-common';
import React, {useRef, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {connect} from '@libs/actions/Delegate';
import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import Avatar from './Avatar';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import MenuItem from './MenuItem';
import type {MenuItemProps} from './MenuItem';
import Popover from './Popover';
import {PressableWithFeedback} from './Pressable';
import Text from './Text';

function AccountSwitcher() {
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const avatarUrl = currentUserPersonalDetails?.avatar ?? '';
const accountID = currentUserPersonalDetails?.accountID ?? -1;
const styles = useThemeStyles();
const {translate} = useLocalize();
const theme = useTheme();
const {canUseNewDotCopilot} = usePermissions();
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const [shouldShowDelegateMenu, setShouldShowDelegateMenu] = useState(false);

const buttonRef = useRef<HTMLDivElement>(null);

const delegates = account?.delegatedAccess?.delegates ?? [];
const shouldShowDelegates = delegates.length > 0 && canUseNewDotCopilot;
const {isSmallScreenWidth} = useResponsiveLayout();

const delegateMenuItems: MenuItemProps[] = delegates.map(({email, role}) => {
const personalDetail = getPersonalDetailByEmail(email);

return {
title: personalDetail?.displayName ?? email,
description: personalDetail?.displayName ? email : '',
badgeText: Str.recapitalize(role),
onPress: () => {
connect(email);
},
avatarID: personalDetail?.accountID ?? -1,
icon: personalDetail?.avatar ?? '',
iconType: CONST.ICON_TYPE_AVATAR,
outerWrapperStyle: isSmallScreenWidth ? {} : styles.accountSwitcherPopover,
numberOfLinesDescription: 1,
};
});

const delegateMenuItemsWithCurrentUser: MenuItemProps[] = [
{
title: currentUserPersonalDetails?.displayName ?? currentUserPersonalDetails?.login,
description: currentUserPersonalDetails?.displayName ? currentUserPersonalDetails?.login : '',
iconRight: Expensicons.Checkmark,
shouldShowRightIcon: true,
success: true,
avatarID: currentUserPersonalDetails?.accountID ?? -1,
icon: avatarUrl,
iconType: CONST.ICON_TYPE_AVATAR,
outerWrapperStyle: isSmallScreenWidth ? {} : styles.accountSwitcherPopover,
numberOfLinesDescription: 1,
wrapperStyle: [styles.buttonDefaultBG],
focused: true,
},
...delegateMenuItems,
];

return (
<>
<PressableWithFeedback
accessible
accessibilityLabel={translate('common.profile')}
onPress={() => {
setShouldShowDelegateMenu(!shouldShowDelegateMenu);
}}
ref={buttonRef}
wrapperStyle={[styles.flexGrow1, styles.accountSwitcherWrapper, styles.justifyContentCenter]}
>
<View style={[styles.flexRow, styles.gap3]}>
<Avatar
type={CONST.ICON_TYPE_AVATAR}
size={CONST.AVATAR_SIZE.MEDIUM}
avatarID={accountID}
source={avatarUrl}
fallbackIcon={currentUserPersonalDetails.fallbackIcon}
/>
<View style={[styles.flex1, styles.flexShrink1, styles.flexBasis0, styles.justifyContentCenter]}>
<View style={[styles.flexRow, styles.gap1]}>
<Text
numberOfLines={1}
style={[styles.textBold]}
>
{currentUserPersonalDetails?.displayName}
</Text>
{shouldShowDelegates && (
<View style={styles.justifyContentCenter}>
<Icon
fill={theme.icon}
src={Expensicons.CaretUpDown}
height={variables.iconSizeSmall}
width={variables.iconSizeSmall}
/>
</View>
)}
</View>
<Text
numberOfLines={1}
style={styles.colorMuted}
>
{currentUserPersonalDetails?.login}
</Text>
</View>
</View>
</PressableWithFeedback>
{shouldShowDelegates && (
<Popover
isVisible={shouldShowDelegateMenu}
onClose={() => setShouldShowDelegateMenu(false)}
anchorRef={buttonRef}
anchorPosition={styles.accountSwitcherAnchorPosition}
>
<View style={styles.pb4}>
<Text style={[styles.createMenuHeaderText, styles.ph5, styles.pb2, styles.pt4]}>{translate('delegate.switchAccount')}</Text>
{delegateMenuItemsWithCurrentUser.map((item) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<MenuItem {...item} />
))}
{/* TODO error handling on API error <Text style={[styles.textLabelError, styles.ph5, styles.pt4]}>Oops something went wrong. Please try again</Text> */}
</View>
</Popover>
)}
</>
);
}

export default AccountSwitcher;
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import Calendar from '@assets/images/calendar.svg';
import Camera from '@assets/images/camera.svg';
import CarWithKey from '@assets/images/car-with-key.svg';
import Car from '@assets/images/car.svg';
import CaretUpDown from '@assets/images/caret-up-down.svg';
import Cash from '@assets/images/cash.svg';
import Chair from '@assets/images/chair.svg';
import ChatBubbleAdd from '@assets/images/chatbubble-add.svg';
Expand Down Expand Up @@ -386,4 +387,5 @@ export {
Filters,
CalendarSolid,
Filter,
CaretUpDown,
};
1 change: 1 addition & 0 deletions src/components/PopoverMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ function PopoverMenu({
renderTooltipContent={item.renderTooltipContent}
numberOfLinesTitle={item.numberOfLinesTitle}
interactive={item.interactive}
badgeText={item.badgeText}
/>
))}
</View>
Expand Down
3 changes: 3 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4369,4 +4369,7 @@ export default {
updateRoomDescription: 'set the room description to:',
clearRoomDescription: 'cleared the room description',
},
delegate: {
switchAccount: 'Switch accounts:',
},
} satisfies TranslationBase;
3 changes: 3 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4889,4 +4889,7 @@ export default {
updateRoomDescription: 'establece la descripción de la sala a:',
clearRoomDescription: 'la descripción de la habitación ha sido borrada',
},
delegate: {
switchAccount: 'Cambiar cuentas:',
},
} satisfies EnglishTranslation;
5 changes: 5 additions & 0 deletions src/libs/API/parameters/ConnectAsDelegateParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type ConnectAsDelegateParams = {
to: string;
};

export default ConnectAsDelegateParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,6 @@ export type {default as ExportSearchItemsToCSVParams} from './ExportSearchItemsT
export type {default as UpdateExpensifyCardLimitParams} from './UpdateExpensifyCardLimitParams';
export type {CreateWorkspaceApprovalParams, UpdateWorkspaceApprovalParams, RemoveWorkspaceApprovalParams} from './WorkspaceApprovalParams';
export type {default as StartIssueNewCardFlowParams} from './StartIssueNewCardFlowParams';
export type {default as ConnectAsDelegateParams} from './ConnectAsDelegateParams';
export type {default as ConfigureExpensifyCardsForPolicyParams} from './ConfigureExpensifyCardsForPolicyParams';
export type {default as CreateExpensifyCardParams} from './CreateExpensifyCardParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = {
REVEAL_EXPENSIFY_CARD_DETAILS: 'RevealExpensifyCardDetails',
SWITCH_TO_OLD_DOT: 'SwitchToOldDot',
TWO_FACTOR_AUTH_VALIDATE: 'TwoFactorAuth_Validate',
CONNECT_AS_DELEGATE: 'ConnectAsDelegate',
} as const;

type SideEffectRequestCommand = ValueOf<typeof SIDE_EFFECT_REQUEST_COMMANDS>;
Expand All @@ -803,6 +804,7 @@ type SideEffectRequestCommandParameters = {
[SIDE_EFFECT_REQUEST_COMMANDS.ADD_PAYMENT_CARD_GBP]: Parameters.AddPaymentCardParams;
[SIDE_EFFECT_REQUEST_COMMANDS.ACCEPT_SPOTNANA_TERMS]: null;
[SIDE_EFFECT_REQUEST_COMMANDS.TWO_FACTOR_AUTH_VALIDATE]: Parameters.ValidateTwoFactorAuthParams;
[SIDE_EFFECT_REQUEST_COMMANDS.CONNECT_AS_DELEGATE]: Parameters.ConnectAsDelegateParams;
};

type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters;
Expand Down
5 changes: 5 additions & 0 deletions src/libs/Permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ function canUseNetSuiteUSATax(betas: OnyxEntry<Beta[]>): boolean {
return !!betas?.includes(CONST.BETAS.NETSUITE_USA_TAX) || canUseAllBetas(betas);
}

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

/**
* Link previews are temporarily disabled.
*/
Expand All @@ -57,4 +61,5 @@ export default {
canUseReportFieldsFeature,
canUseWorkspaceFeeds,
canUseNetSuiteUSATax,
canUseNewDotCopilot,
};
35 changes: 35 additions & 0 deletions src/libs/actions/Delegate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types';
import * as NetworkStore from '@libs/Network/NetworkStore';
import * as SequentialQueue from '@libs/Network/SequentialQueue';
import {openApp} from './App';
import updateSessionAuthTokens from './Session/updateSessionAuthTokens';

function connect(email: string) {
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.CONNECT_AS_DELEGATE, {to: email}, {})
.then((response) => {
if (!response?.restrictedToken || !response?.encryptedAuthToken) {
// TODO: Show an error message to the user
console.error('Error during connect: No authToken returned');
return;
}
return SequentialQueue.waitForIdle()
.then(() => Onyx.clear())
.then(() => {
// Update authToken in Onyx and in our local variables so that API requests will use the new authToken
updateSessionAuthTokens(response?.restrictedToken, response?.encryptedAuthToken);

NetworkStore.setAuthToken(response?.restrictedToken ?? null);
openApp();
});
})
.catch((error) => {
// TODO: Show an error message to the user
console.error('Error during connect:', error);
});
}

// eslint-disable-next-line import/prefer-default-export
export {connect};
Loading
Loading