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

upcoming: [M3-7429] - Add Parent/Proxy 'Switch Account' button and drawer to user profile dropdown menu #10031

Merged
merged 13 commits into from
Jan 8, 2024
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add parent/proxy 'Switch Account' button and drawer to user profile dropdown menu ([#10031](https://github.com/linode/manager/pull/10031))
3 changes: 3 additions & 0 deletions packages/manager/src/assets/icons/swapSmall.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions packages/manager/src/features/Account/SwitchAccountButton.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The SwitchAccountButton will be reused on the Account landing page for parent/proxy users. (M3-7475)

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from 'react';

import SwapIcon from 'src/assets/icons/swapSmall.svg';
import { Button, ButtonProps } from 'src/components/Button/Button';

export const SwitchAccountButton = (props: ButtonProps) => {
return (
<Button startIcon={<SwapIcon />} {...props}>
Switch Account
</Button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { fireEvent, within } from '@testing-library/react';
import * as React from 'react';

import { accountFactory } from 'src/factories/account';
import { accountUserFactory } from 'src/factories/accountUsers';
import { makeResourcePage } from 'src/mocks/serverHandlers';
import { rest, server } from 'src/mocks/testServer';
import { renderWithTheme } from 'src/utilities/testHelpers';

import { SwitchAccountDrawer } from './SwitchAccountDrawer';

const props = {
onClose: vi.fn(),
open: true,
username: 'mock-user',
};

describe('SwitchAccountDrawer', () => {
it('should have a title', () => {
const { getByText } = renderWithTheme(<SwitchAccountDrawer {...props} />);
expect(getByText('Switch Account')).toBeInTheDocument();
});

it('should display helper text about the accounts', () => {
const { getByText } = renderWithTheme(<SwitchAccountDrawer {...props} />);
expect(
getByText(
'Select an account to view and manage its settings and configurations',
{ exact: false }
)
).toBeInTheDocument();
});

it('should include a link to switch back to the parent account if the active user is a proxy user', async () => {
server.use(
rest.get('*/account/users/*', (req, res, ctx) => {
return res(ctx.json(accountUserFactory.build({ user_type: 'proxy' })));
})
);

const { findByLabelText, getByText } = renderWithTheme(
<SwitchAccountDrawer {...props} />
);

expect(
getByText(
'Select an account to view and manage its settings and configurations',
{ exact: false }
)
).toBeInTheDocument();
expect(await findByLabelText('parent-account-link')).toHaveTextContent(
'switch back to your account'
);
});

it('should display a list of child accounts', async () => {
server.use(
rest.get('*/account/child-accounts', (req, res, ctx) => {
return res(
ctx.json(
makeResourcePage(
accountFactory.buildList(5, { company: 'Child Co.' })
)
)
);
})
);

const { findByTestId } = renderWithTheme(
<SwitchAccountDrawer {...props} />
);

const childAccounts = await findByTestId('child-account-list');
expect(
within(childAccounts).getAllByText('Child Co.', { exact: false })
).toHaveLength(5);
});

it('should close when the close icon is clicked', () => {
const { getByLabelText } = renderWithTheme(
<SwitchAccountDrawer {...props} />
);

const closeIconButton = getByLabelText('Close drawer');
fireEvent.click(closeIconButton);

expect(props.onClose).toHaveBeenCalledTimes(1);
});
});
104 changes: 104 additions & 0 deletions packages/manager/src/features/Account/SwitchAccountDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Typography, styled } from '@mui/material';
import { AxiosHeaders } from 'axios';
import React from 'react';

import { StyledLinkButton } from 'src/components/Button/StyledLinkButton';
import { CircleProgress } from 'src/components/CircleProgress';
import { Drawer } from 'src/components/Drawer';
import { Notice } from 'src/components/Notice/Notice';
import { Stack } from 'src/components/Stack';
import { useFlags } from 'src/hooks/useFlags';
import { useChildAccounts } from 'src/queries/account';
import { useAccountUser } from 'src/queries/accountUsers';
import { useProfile } from 'src/queries/profile';
import { authentication } from 'src/utilities/storage';

interface Props {
onClose: () => void;
open: boolean;
username: string;
}

export const SwitchAccountDrawer = (props: Props) => {
const { onClose, open } = props;

const flags = useFlags();

const handleClose = () => {
onClose();
};

const { data: profile } = useProfile();
const { data: user } = useAccountUser(profile?.username ?? '');

// From proxy accounts, make a request on behalf of the parent account to fetch child accounts.
const headers =
flags.parentChildAccountAccess && user?.user_type === 'proxy'
? new AxiosHeaders({ Authorization: authentication.token.get() }) // TODO: Parent/Child - M3-7430: replace this token with the parent token in local storage.
: undefined;
const { data: childAccounts, error, isLoading } = useChildAccounts({
headers,
});

const renderChildAccounts = React.useCallback(() => {
if (isLoading) {
return <CircleProgress mini />;
}

if (childAccounts?.results === 0) {
return <Notice variant="info">There are no child accounts.</Notice>;
}

if (error) {
return (
<Notice variant="error">
There was an error loading child accounts.
</Notice>
);
}

return childAccounts?.data.map((childAccount, idx) => (
<StyledChildAccountLinkButton
onClick={() => {
// TODO: Parent/Child - M3-7430
// handleAccountSwitch();
}}
key={`child-account-link-button-${idx}`}
>
{childAccount.company}
</StyledChildAccountLinkButton>
));
}, [childAccounts, error, isLoading]);

return (
<Drawer onClose={handleClose} open={open} title="Switch Account">
<StyledTypography>
Select an account to view and manage its settings and configurations
{user?.user_type === 'proxy' && (
<>
{' '}
or {/* TODO: Parent/Child - M3-7430 */}
<StyledLinkButton
aria-label="parent-account-link"
onClick={() => null}
>
switch back to your account
</StyledLinkButton>
</>
)}
.
</StyledTypography>
<Stack alignItems={'flex-start'} data-testid="child-account-list">
{renderChildAccounts()}
</Stack>
</Drawer>
);
};

const StyledTypography = styled(Typography)(({ theme }) => ({
margin: `${theme.spacing(3)} 0`,
}));

const StyledChildAccountLinkButton = styled(StyledLinkButton)(({ theme }) => ({
marginBottom: theme.spacing(2),
}));
Loading