Skip to content

Commit

Permalink
Merge pull request #483 from cloud-gov/eoc/468-data
Browse files Browse the repository at this point in the history
Functional users list overlays
  • Loading branch information
echappen authored Sep 13, 2024
2 parents dd57736 + 0bdf344 commit c70cf7b
Show file tree
Hide file tree
Showing 23 changed files with 518 additions and 127 deletions.
1 change: 1 addition & 0 deletions .env.example.local
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ CF_API_TOKEN=
# Prefixing a variable with NEXT_PUBLIC_ will make it available to the browser:
# https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser
NEXT_PUBLIC_USER_INVITE_URL=https://account.dev.us-gov-west-1.aws-us-gov.cloud.gov/invite
NEXT_PUBLIC_CLOUD_SUPPORT_URL="mailto:[email protected]?body=What+is+your+question%3F%0D%0A%0D%0APlease+provide+your+application+name+or+URL.+Do+not+include+any+sensitive+information+about+your+platform+in+this+email."
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,7 @@ describe('edit org roles actions', () => {
// act/expect
expect(async () => {
await updateOrgRolesForUser(userGuid, orgGuid, roles);
}).rejects.toThrow(
new Error('Unable to edit org role. Please try again.')
);
}).rejects.toThrow(new Error('Try submitting your changes again.'));
});
});
});
Expand Down
22 changes: 4 additions & 18 deletions __tests__/components/UserAccount/Username.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,9 @@ import { render, screen } from '@testing-library/react';
import { Username } from '@/components/UserAccount/Username';

describe('Username', () => {
it('when given a user, displays the user name', () => {
// setup
const mockUser = {
guid: 'userguid',
username: 'User 1',
};
it('when given a username, displays it', () => {
// act
render(<Username user={mockUser} />);
render(<Username username="User 1" />);
const content = screen.getByText('User 1');
// assert
expect(content).toBeInTheDocument();
Expand All @@ -23,16 +18,12 @@ describe('Username', () => {
it('when given a service account user, displays the service account name', () => {
// setup
const guid = 'cafce0be-62dd-4f02-9770-d546c8714430';
const mockUser = {
guid: 'userguid',
username: guid,
};
const mockAccount = {
guid: guid,
name: 'Auditor 1',
};
// act
render(<Username user={mockUser} serviceAccount={mockAccount} />);
render(<Username username="foo user" serviceAccount={mockAccount} />);
const auditor = screen.getByText(/Auditor 1/);
const svcAcct = screen.getByText(/service/);
const username = screen.queryByText(guid);
Expand All @@ -43,13 +34,8 @@ describe('Username', () => {
});

it('when given a user without a username, displays default text', () => {
// setup
const mockUser = {
guid: 'userguid',
username: '',
};
// act
render(<Username user={mockUser} />);
render(<Username username="" />);
const content = screen.getByText('Unnamed user');
// assert
expect(content).toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('UsersActionsOrgRoles', () => {
expect(screen.getByText(/Billing manager/)).toBeInTheDocument()
);
// query
const submitBtn = screen.getByText('Save');
const submitBtn = screen.getByText('Update roles');
// act
fireEvent.click(submitBtn);
// expect a success message
Expand Down
18 changes: 17 additions & 1 deletion __tests__/components/UsersList/UsersList.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @jest-environment jsdom
*/
import { describe, expect, it } from '@jest/globals';
import { describe, expect, it, beforeAll } from '@jest/globals';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UsersList } from '@/components/UsersList/UsersList';
Expand Down Expand Up @@ -68,6 +68,22 @@ const mockUserLogonTime = {
};

describe('UsersList', () => {
beforeAll(() => {
/* global jest */
/* eslint no-undef: "off" */
HTMLDialogElement.prototype.show = jest.fn(function () {
this.open = true;
});

HTMLDialogElement.prototype.showModal = jest.fn(function () {
this.open = true;
});

HTMLDialogElement.prototype.close = jest.fn(function () {
this.open = false;
});
/* eslint no-undef: "error" */
});
it('sorts all users by username in alpha order', () => {
// act
render(
Expand Down
2 changes: 1 addition & 1 deletion src/app/orgs/[orgId]/users/[userId]/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export async function updateSpaceRolesForUser(
logDevError(
`api error on cf edit spaces page with http code ${response.status} for url: ${response.url}`
);
throw new Error('Unable to edit space role. Please try again.');
throw new Error('Try submitting your changes again.');
}
return response.headers.get('Location');
});
Expand Down
7 changes: 6 additions & 1 deletion src/app/orgs/[orgId]/users/[userId]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ export default async function SpaceLayout({
</div>
<div className="margin-top-3">
<PageHeader
heading={<Username user={user} serviceAccount={serviceAccount} />}
heading={
<Username
username={user.username}
serviceAccount={serviceAccount}
/>
}
/>
</div>
{children}
Expand Down
2 changes: 1 addition & 1 deletion src/app/orgs/[orgId]/users/[userId]/org-roles/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export async function updateOrgRolesForUser(
logDevError(
`api error on cf edit org page with http code ${response.status} for url: ${response.url}`
);
throw new Error('Unable to edit org role. Please try again.');
throw new Error('Try submitting your changes again.');
}
return response.headers.get('Location');
});
Expand Down
68 changes: 67 additions & 1 deletion src/assets/stylesheets/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,24 @@
),

$background-color-palettes: (
'palette-color-system-mint-medium' // no trailing comma
'palette-color-system-mint-medium',
'palette-color-system-green-cool-vivid',
'palette-color-system-red-cool-vivid' // no trailing comma
),

$border-color-palettes: (
'palette-color-system-green-cool',
'palette-color-system-red-vivid' // no trailing comma
),

$top-palettes: (
'palette-units-system-positive'
),

$bottom-palettes: (
'palette-units-system-positive'
),

$right-palettes: (
'palette-units-system-positive'
),
Expand Down Expand Up @@ -312,3 +323,58 @@ dialog.overlayDrawer[open]::backdrop {
background-color: rgb(0 0 0 / 0%);
}
}

// USA Checkbox

.usa-checkbox {
background: initial; // removes white bg provided by USWDS
}

.usa-checkbox__label::before {
background: initial;
box-shadow: 0 0 0 2px color('primary');
}

// primary color #2C608A

// USA Alerts

$success-color-light: 'green-cool-5v';
$success-color-dark: 'green-cool-50';
$error-color-light: 'red-cool-10v';
$error-color-dark: 'red-40v';

.usa-alert .usa-alert__body {
max-width: none;
}

.usa-alert .usa-alert__body h4 {
@include u-font-size('sans', 'md');
margin-bottom: units(2px);
}

.usa-alert--success {
@include u-bg($success-color-light);
border-left-color: color($success-color-dark);

.usa-alert__body {
@include u-bg($success-color-light);

&:before {
@include u-bg($success-color-dark); // icon color
}
}
}

.usa-alert--error {
@include u-bg($error-color-light);
border-left-color: color($error-color-dark);

.usa-alert__body {
@include u-bg($error-color-light);

&:before {
@include u-bg($error-color-dark); // icon color
}
}
}
5 changes: 1 addition & 4 deletions src/components/GridList/GridListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import React from 'react';

export function GridListItem({ children }: { children: React.ReactNode }) {
return (
<div
role="listitem"
className="border padding-x-5 padding-y-3 radius-md border-base-lighter bg-white"
>
<div role="listitem" className="padding-y-3">
{children}
</div>
);
Expand Down
67 changes: 47 additions & 20 deletions src/components/OverlayDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import Image from 'next/image';
import closeIcon from '@/../public/img/uswds/usa-icons/close.svg';

export function OverlayDrawer({
ariaLabel = 'dialog', // should announce the purpose of the dialog when opening
children,
close, // function for dialog close button that should change the isOpen prop from the parent
id,
id = 'overlay-drawer', // helps distinguish which overlay drawer in case there are multiple on the page
isOpen = false,
}: {
ariaLabel?: string;
children: React.ReactNode;
close: Function;
id: string;
id?: string;
isOpen: boolean;
}) {
const dialogRef = useRef<HTMLDialogElement>(null);
Expand All @@ -31,34 +33,59 @@ export function OverlayDrawer({
close();
}
};
window.addEventListener('keydown', handleEscapeKeyPress);
// close when clicking outside dialog
const clicked = (e: MouseEvent) => {
const target = e.target as HTMLElement;
// Clicking #dialog-body will not trigger this, only the id of the dialog itself.
// #dialog-body must cover the full open dialog area for this to work.
if (isOpen && target?.id?.match(id)) {
close();
}
};
const addListeners = () => {
window.addEventListener('keydown', handleEscapeKeyPress);
window.addEventListener('click', clicked);
};
const removeListeners = () => {
window.removeEventListener('keydown', handleEscapeKeyPress);
window.removeEventListener('click', clicked);
};
if (isOpen) {
addListeners();
}

return () => {
window.removeEventListener('keydown', handleEscapeKeyPress);
removeListeners();
};
}, [close, isOpen]);
}, [close, id, isOpen]);

return (
<dialog
id={id}
className="overlayDrawer height-full maxh-none tablet-lg:width-tablet-lg maxw-none padding-y-10 tablet-lg:padding-y-15 padding-right-1 tablet-lg:padding-right-4 padding-left-3 tablet-lg:padding-left-10 bg-accent-warm-light border-accent-cool tablet-lg:border-accent-cool border-left-1 tablet-lg:border-left-105 border-right-0 border-top-0 border-bottom-0"
className="overlayDrawer height-full maxh-none tablet-lg:width-tablet-lg maxw-none border-0 padding-0"
ref={dialogRef}
aria-label={ariaLabel}
>
<div style={{ overscrollBehavior: 'contain' }}>{children}</div>
<button
type="button"
className="usa-button usa-modal__close position-fixed top-7 right-4"
aria-label="Close this dialog"
onClick={() => close()}
<div
id="dialog-body"
className="minh-full padding-y-10 tablet-lg:padding-y-15 padding-right-1 tablet-lg:padding-right-4 padding-left-3 tablet-lg:padding-left-10 bg-accent-warm-light border-accent-cool tablet-lg:border-accent-cool border-left-1 tablet-lg:border-left-105 border-right-0 border-top-0 border-bottom-0"
>
<Image
unoptimized
src={closeIcon}
alt="Close this dialog"
width="32"
height="32"
/>
</button>
<button
type="button"
className="usa-button usa-modal__close position-fixed top-7 right-4"
aria-label="Close this dialog"
onClick={() => close()}
>
<Image
unoptimized
src={closeIcon}
alt="Close this dialog"
width="32"
height="32"
/>
</button>
<div>{children}</div>
</div>
</dialog>
);
}
41 changes: 41 additions & 0 deletions src/components/Overlays/OrgUserOrgRolesOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { UsersActionsOrgRoles } from '@/components/UsersActions/UsersActionsOrgRoles';
import { UserOrgPage } from '@/controllers/controller-types';
import { ServiceCredentialBindingObj } from '@/api/cf/cloudfoundry-types';
import { OverlayHeaderUsername } from './OverlayHeaderUsername';

export function OrgUserOrgRolesOverlay({
orgGuid,
user,
onCancel,
onSuccess,
serviceAccount,
}: {
orgGuid: string;
user?: UserOrgPage | undefined | null;
onCancel: Function;
onSuccess: Function;
serviceAccount?: ServiceCredentialBindingObj | undefined | null;
}) {
if (!user) return null;
return (
<>
<OverlayHeaderUsername
header="update organization roles"
serviceAccount={serviceAccount}
username={user.username}
/>
<div className="usa-prose">
<p>
By assigning specific roles, you can grant a user access to specific
information and features within a given organization.
</p>
<UsersActionsOrgRoles
orgGuid={orgGuid}
userGuid={user.guid}
onCancel={onCancel}
onSuccess={onSuccess}
/>
</div>
</>
);
}
Loading

0 comments on commit c70cf7b

Please sign in to comment.