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

Delegate access: Add account switcher #47338

Merged
merged 101 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 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
9a2a35d
add gap
rushatgabhane Aug 13, 2024
c3e6363
use menuitem list and delegators
rushatgabhane Aug 14, 2024
fb29be1
correct docs
rushatgabhane Aug 14, 2024
477ecb6
single execution
rushatgabhane Aug 14, 2024
46a62e4
import style
rushatgabhane Aug 14, 2024
ebce5ec
change skeleton to fit account switcher
rushatgabhane Aug 14, 2024
8f4b5b3
rm padding from avatar skeleton
rushatgabhane Aug 14, 2024
3d12f3f
avatar size medium
rushatgabhane Aug 14, 2024
f0500f5
cleanup
rushatgabhane Aug 14, 2024
580056e
add type for delegate
rushatgabhane Aug 14, 2024
c0468bf
add type delegate role
rushatgabhane Aug 14, 2024
41da8e8
add mapping for delegate role
rushatgabhane Aug 14, 2024
4e4ff40
move role to const
rushatgabhane Aug 14, 2024
22fb81d
show full or limited as role
rushatgabhane Aug 14, 2024
65df1c2
show full or limited as role
rushatgabhane Aug 14, 2024
4a30ea6
add error msg to translate
rushatgabhane Aug 14, 2024
19f2501
show error message in account switcher
rushatgabhane Aug 14, 2024
97e581d
add onyx data to handle errors
rushatgabhane Aug 14, 2024
313e8e9
add error type
rushatgabhane Aug 14, 2024
91f2ecf
clear errors after closing popover
rushatgabhane Aug 14, 2024
ba51bba
add func to clear errors
rushatgabhane Aug 14, 2024
0576180
cleanup
rushatgabhane Aug 14, 2024
0312b7c
add min width 0
rushatgabhane Aug 14, 2024
f2da059
cleanup
rushatgabhane Aug 14, 2024
c6c9255
Merge branch 'main' of github.com:rushatgabhane/exfy into account-swi…
rushatgabhane Aug 15, 2024
964fdd5
preserve keys for copilot
rushatgabhane Aug 15, 2024
c38d798
move to const
rushatgabhane Aug 15, 2024
413e7a7
cleanup
rushatgabhane Aug 15, 2024
6b31b0a
fix style
rushatgabhane Aug 15, 2024
cbb9147
show disconnect menu items
rushatgabhane Aug 15, 2024
4328d57
add disconnect command type
rushatgabhane Aug 15, 2024
711f838
add disconnect command
rushatgabhane Aug 15, 2024
1c630e5
add deps array
rushatgabhane Aug 15, 2024
cb09048
disable interactivity when no copilot
rushatgabhane Aug 15, 2024
1d487f4
Merge branch 'main' of github.com:rushatgabhane/exfy into account-swi…
rushatgabhane Aug 15, 2024
abb1570
use authtoekn for disconnect
rushatgabhane Aug 15, 2024
0ba3a8e
rename to account switcher skeleton view
rushatgabhane Aug 16, 2024
e342c0c
rename to start pos x
rushatgabhane Aug 16, 2024
75c01ef
handle errors when disconnecting as a copilot
rushatgabhane Aug 16, 2024
c225b8e
add deps arr
rushatgabhane Aug 16, 2024
1d2198a
cleanup: extract common logic
rushatgabhane Aug 16, 2024
0998819
moreee cleanup: extract common logic
rushatgabhane Aug 16, 2024
ceb5837
Merge branch 'main' of github.com:rushatgabhane/exfy into account-swi…
rushatgabhane Aug 16, 2024
baacd65
green checkmark
rushatgabhane Aug 16, 2024
805c443
tidy up
rushatgabhane Aug 16, 2024
fa4c0df
use narrow layout
rushatgabhane Aug 16, 2024
3d07403
handle offline: show confirm modal if offline
rushatgabhane Aug 16, 2024
9418dd2
Merge branch 'main' into account-switcher
rushatgabhane Aug 21, 2024
f8f72ef
fix lang
rushatgabhane Aug 21, 2024
7135232
use common prompt
rushatgabhane Aug 21, 2024
fd30e2a
add key to menu item list
rushatgabhane Aug 21, 2024
0650d79
add key to account switcher items
rushatgabhane Aug 21, 2024
266f3a0
rm dimming
rushatgabhane Aug 25, 2024
581d64c
add display name
rushatgabhane Aug 25, 2024
857528a
add lang
rushatgabhane Aug 25, 2024
653e06b
add lang
rushatgabhane Aug 25, 2024
64c9c79
merge main
rushatgabhane Aug 25, 2024
104de99
add comment
rushatgabhane Aug 25, 2024
9d6c29b
fix dupe emails when connecting and disconnecting
rushatgabhane Aug 25, 2024
b0fc5cf
add type
rushatgabhane Aug 27, 2024
0599ad1
Merge branch 'main' of github.com:rushatgabhane/exfy into account-swi…
rushatgabhane Aug 27, 2024
6059095
fix skeleton height
rushatgabhane Aug 27, 2024
e36cb66
confirm app is ready to open
rushatgabhane Aug 28, 2024
174bece
hide popover on connect / disconnect
rushatgabhane Aug 28, 2024
b40f16d
Merge branch 'main' of github.com:rushatgabhane/exfy into account-swi…
rushatgabhane Aug 28, 2024
1646d07
use Modal.close to handle race condition
rushatgabhane Aug 28, 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.
5 changes: 5 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ const CONST = {
REPORT_FIELDS_FEATURE: 'reportFieldsFeature',
WORKSPACE_FEEDS: 'workspaceFeeds',
NETSUITE_USA_TAX: 'netsuiteUsaTax',
NEW_DOT_COPILOT: 'newDotCopilot',
WORKSPACE_RULES: 'workspaceRules',
COMBINED_TRACK_SUBMIT: 'combinedTrackSubmit',
},
Expand Down Expand Up @@ -3877,6 +3878,10 @@ const CONST = {
ENABLED: 'ENABLED',
DISABLED: 'DISABLED',
},
DELEGATE_ROLE: {
SUBMITTER: 'submitter',
ALL: 'all',
},
STRIPE_GBP_AUTH_STATUSES: {
SUCCEEDED: 'succeeded',
CARD_AUTHENTICATION_REQUIRED: 'authentication_required',
Expand Down
202 changes: 202 additions & 0 deletions src/components/AccountSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
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 useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import {clearDelegatorErrors, connect, disconnect} from '@libs/actions/Delegate';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import variables from '@styles/variables';
import * as Modal from '@userActions/Modal';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetails} from '@src/types/onyx';
import Avatar from './Avatar';
import ConfirmModal from './ConfirmModal';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import type {MenuItemProps} from './MenuItem';
import MenuItemList from './MenuItemList';
import type {MenuItemWithLink} from './MenuItemList';
import Popover from './Popover';
import {PressableWithFeedback} from './Pressable';
import Text from './Text';

function AccountSwitcher() {
dangrous marked this conversation as resolved.
Show resolved Hide resolved
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const {canUseNewDotCopilot} = usePermissions();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const buttonRef = useRef<HTMLDivElement>(null);

const [shouldShowDelegatorMenu, setShouldShowDelegatorMenu] = useState(false);
const [shouldShowOfflineModal, setShouldShowOfflineModal] = useState(false);
const delegators = account?.delegatedAccess?.delegators ?? [];

const isActingAsDelegate = !!account?.delegatedAccess?.delegate ?? false;
const canSwitchAccounts = canUseNewDotCopilot && (delegators.length > 0 || isActingAsDelegate);

const createBaseMenuItem = (personalDetails: PersonalDetails | undefined, error?: TranslationPaths, additionalProps: MenuItemWithLink = {}): MenuItemWithLink => {
return {
title: personalDetails?.displayName ?? personalDetails?.login,
description: personalDetails?.login,
Copy link
Contributor

Choose a reason for hiding this comment

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

Coming from #48648, we should have used Str.removeSMSDomain to ensure phone numbers are displayed correctly (without the @expensify.sms suffix).

avatarID: personalDetails?.accountID ?? -1,
icon: personalDetails?.avatar ?? '',
iconType: CONST.ICON_TYPE_AVATAR,
outerWrapperStyle: shouldUseNarrowLayout ? {} : styles.accountSwitcherPopover,
numberOfLinesDescription: 1,
errorText: error ? translate(error) : '',
shouldShowRedDotIndicator: !!error,
errorTextStyle: styles.mt2,
...additionalProps,
};
};

const menuItems = (): MenuItemProps[] => {
rushatgabhane marked this conversation as resolved.
Show resolved Hide resolved
const currentUserMenuItem = createBaseMenuItem(currentUserPersonalDetails, undefined, {
wrapperStyle: [styles.buttonDefaultBG],
focused: true,
shouldShowRightIcon: true,
iconRight: Expensicons.Checkmark,
success: true,
key: `${currentUserPersonalDetails?.login}-current`,
});

if (isActingAsDelegate) {
const delegateEmail = account?.delegatedAccess?.delegate ?? '';

// Avoid duplicating the current user in the list when switching accounts
if (delegateEmail === currentUserPersonalDetails.login) {
return [currentUserMenuItem];
}

const delegatePersonalDetails = PersonalDetailsUtils.getPersonalDetailByEmail(delegateEmail);
const error = account?.delegatedAccess?.error;

return [
createBaseMenuItem(delegatePersonalDetails, error, {
onPress: () => {
if (isOffline) {
Modal.close(() => setShouldShowOfflineModal(true));
return;
}
disconnect();
},
key: `${delegateEmail}-delegate`,
}),
currentUserMenuItem,
];
}

const delegatorMenuItems: MenuItemProps[] = delegators
.filter(({email}) => email !== currentUserPersonalDetails.login)
.map(({email, role, error}, index) => {
const personalDetails = PersonalDetailsUtils.getPersonalDetailByEmail(email);
return createBaseMenuItem(personalDetails, error, {
badgeText: translate('delegate.role', role),
onPress: () => {
if (isOffline) {
Modal.close(() => setShouldShowOfflineModal(true));
return;
}
connect(email);
},
key: `${email}-${index}`,
});
});

return [currentUserMenuItem, ...delegatorMenuItems];
};

return (
<>
<PressableWithFeedback
accessible
accessibilityLabel={translate('common.profile')}
onPress={() => {
setShouldShowDelegatorMenu(!shouldShowDelegatorMenu);
}}
ref={buttonRef}
interactive={canSwitchAccounts}
wrapperStyle={[styles.flexGrow1, styles.flex1, styles.mnw0, styles.justifyContentCenter]}
>
<View style={[styles.flexRow, styles.gap3]}>
<Avatar
type={CONST.ICON_TYPE_AVATAR}
size={CONST.AVATAR_SIZE.MEDIUM}
avatarID={currentUserPersonalDetails?.accountID}
source={currentUserPersonalDetails?.avatar}
fallbackIcon={currentUserPersonalDetails.fallbackIcon}
/>
<View style={[styles.flex1, styles.flexShrink1, styles.flexBasis0, styles.justifyContentCenter, styles.gap1]}>
<View style={[styles.flexRow, styles.gap1]}>
<Text
numberOfLines={1}
style={[styles.textBold, styles.textLarge]}
>
{currentUserPersonalDetails?.displayName}
</Text>
{canSwitchAccounts && (
<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, styles.fontSizeLabel]}
>
{currentUserPersonalDetails?.login}
</Text>
</View>
</View>
</PressableWithFeedback>
{canSwitchAccounts && (
<Popover
isVisible={shouldShowDelegatorMenu}
onClose={() => {
setShouldShowDelegatorMenu(false);
clearDelegatorErrors();
}}
anchorRef={buttonRef}
anchorPosition={styles.accountSwitcherAnchorPosition}
>
<View style={styles.pb4}>
<Text style={[styles.createMenuHeaderText, styles.ph5, styles.pb2, styles.pt4]}>{translate('delegate.switchAccount')}</Text>
<MenuItemList
menuItems={menuItems()}
shouldUseSingleExecution
/>
</View>
</Popover>
)}
<ConfirmModal
title={translate('common.youAppearToBeOffline')}
isVisible={shouldShowOfflineModal}
onConfirm={() => setShouldShowOfflineModal(false)}
onCancel={() => setShouldShowOfflineModal(false)}
confirmText={translate('common.buttonConfirm')}
prompt={translate('common.offlinePrompt')}
shouldShowCancelButton={false}
/>
</>
);
}

AccountSwitcher.displayName = 'AccountSwitcher';

export default AccountSwitcher;
rushatgabhane marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,53 @@ import SkeletonViewContentLoader from '@components/SkeletonViewContentLoader';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import CONST from '@src/CONST';

type CurrentUserPersonalDetailsSkeletonViewProps = {
type AccountSwitcherSkeletonViewProps = {
/** Whether to animate the skeleton view */
shouldAnimate?: boolean;

/** The size of the avatar */
avatarSize?: ValueOf<typeof CONST.AVATAR_SIZE>;
};

function CurrentUserPersonalDetailsSkeletonView({shouldAnimate = true, avatarSize = CONST.AVATAR_SIZE.LARGE}: CurrentUserPersonalDetailsSkeletonViewProps) {
function AccountSwitcherSkeletonView({shouldAnimate = true, avatarSize = CONST.AVATAR_SIZE.LARGE}: AccountSwitcherSkeletonViewProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const avatarPlaceholderSize = StyleUtils.getAvatarSize(avatarSize);
const avatarPlaceholderRadius = avatarPlaceholderSize / 2;
const spaceBetweenAvatarAndHeadline = styles.mb3.marginBottom + styles.mt1.marginTop + (variables.lineHeightXXLarge - variables.fontSizeXLarge) / 2;
const headlineSize = variables.fontSizeXLarge;
const spaceBetweenHeadlineAndLabel = styles.mt1.marginTop + (variables.lineHeightXXLarge - variables.fontSizeXLarge) / 2;
const labelSize = variables.fontSizeLabel;
const startPositionX = 30;
Copy link
Contributor

Choose a reason for hiding this comment

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

Using an absolute start position caused a mismatch between skeleton view and the loaded view. More details here. #49548


return (
<View style={styles.avatarSectionWrapperSkeleton}>
<SkeletonViewContentLoader
animate={shouldAnimate}
backgroundColor={theme.skeletonLHNIn}
foregroundColor={theme.skeletonLHNOut}
height={avatarPlaceholderSize + spaceBetweenAvatarAndHeadline + headlineSize + spaceBetweenHeadlineAndLabel + labelSize}
height={avatarPlaceholderSize + styles.pb3.paddingBottom}
>
<Circle
cx="50%"
cx={startPositionX}
cy={avatarPlaceholderRadius}
r={avatarPlaceholderRadius}
/>
<Rect
x="20%"
y={avatarPlaceholderSize + spaceBetweenAvatarAndHeadline}
width="60%"
height={headlineSize}
x={startPositionX + avatarPlaceholderRadius + styles.gap3.gap}
y="11"
width="45%"
height="8"
/>
<Rect
x="15%"
y={avatarPlaceholderSize + spaceBetweenAvatarAndHeadline + headlineSize + spaceBetweenHeadlineAndLabel}
width="70%"
height={labelSize}
x={startPositionX + avatarPlaceholderRadius + styles.gap3.gap}
y="31"
width="55%"
height="8"
/>
</SkeletonViewContentLoader>
</View>
);
}

CurrentUserPersonalDetailsSkeletonView.displayName = 'CurrentUserPersonalDetailsSkeletonView';
export default CurrentUserPersonalDetailsSkeletonView;
AccountSwitcherSkeletonView.displayName = 'AccountSwitcherSkeletonView';
export default AccountSwitcherSkeletonView;
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 @@ -387,5 +388,6 @@ export {
Filters,
CalendarSolid,
Filter,
CaretUpDown,
rushatgabhane marked this conversation as resolved.
Show resolved Hide resolved
Feed,
};
5 changes: 4 additions & 1 deletion src/components/MenuItemList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ type MenuItemLink = string | (() => Promise<string>);
type MenuItemWithLink = MenuItemProps & {
/** The link to open when the menu item is clicked */
link?: MenuItemLink;

/** A unique key for the menu item */
key?: string;
};

type MenuItemListProps = {
Expand Down Expand Up @@ -43,7 +46,7 @@ function MenuItemList({menuItems = [], shouldUseSingleExecution = false}: MenuIt
<>
{menuItems.map((menuItemProps) => (
<MenuItem
key={menuItemProps.title}
key={menuItemProps.key ?? menuItemProps.title}
onSecondaryInteraction={menuItemProps.link !== undefined ? (e) => secondaryInteraction(menuItemProps.link, e) : undefined}
ref={popoverAnchor}
shouldBlockSelection={!!menuItemProps.link}
Expand Down
1 change: 1 addition & 0 deletions src/components/PopoverMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ function PopoverMenu({
renderTooltipContent={item.renderTooltipContent}
numberOfLinesTitle={item.numberOfLinesTitle}
interactive={item.interactive}
badgeText={item.badgeText}
/>
))}
</View>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ function Search({queryJSON, isCustomQuery}: SearchProps) {
/>
<DecisionModal
title={translate('common.youAppearToBeOffline')}
prompt={translate('search.offlinePrompt')}
prompt={translate('common.offlinePrompt')}
isSmallScreenWidth={isSmallScreenWidth}
onSecondOptionSubmit={() => setOfflineModalVisible(false)}
secondOptionText={translate('common.buttonConfirm')}
Expand Down
Loading
Loading