From 8b0760a6d129ecc4bf8ed1f0f300b51f14f3e768 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 26 May 2020 14:50:39 +0200 Subject: [PATCH] KEYCLOAK-14158 Polished the My Resource page empty state change case added dropdown menu instead of buttons now on edit you can add and remove permissions changed how the actions work updated success messages use live region alerts toast alerts username or email search labels for the buttons margin between accecpt and deny button fixed test and types changed to bigger distance with split component changed to use seperate empty state component --- .../account/resources/ResourceService.java | 7 +- .../ui/account2/page/MyResourcesPage.java | 20 +- .../account/messages/messages_en.properties | 5 + .../account/src/app/content/ContentAlert.tsx | 78 +++--- .../my-resources-page/EditTheResource.tsx | 126 ++++----- .../my-resources-page/MyResourcesPage.tsx | 2 +- .../my-resources-page/PermissionRequest.tsx | 39 ++- .../my-resources-page/PermissionSelect.tsx | 108 ++++++++ .../my-resources-page/ResourcesTable.tsx | 261 +++++++++++++----- .../my-resources-page/ShareTheResource.tsx | 65 +---- .../SharedResourcesTable.tsx | 12 +- .../content/page-not-found/PageNotFound.tsx | 14 +- .../src/app/widgets/EmptyMessageState.tsx | 53 ++++ .../account/src/package-lock.json | 12 +- .../keycloak-preview/account/src/package.json | 1 + 15 files changed, 547 insertions(+), 256 deletions(-) create mode 100644 themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/PermissionSelect.tsx create mode 100644 themes/src/main/resources/theme/keycloak-preview/account/src/app/widgets/EmptyMessageState.tsx diff --git a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java index 2a50849e5b..07d6e1984a 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java @@ -19,6 +19,7 @@ package org.keycloak.services.resources.account.resources; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; @@ -205,7 +206,11 @@ public class ResourceService extends AbstractResourceService { UserModel user = users.getUserByUsername(requester, provider.getRealm()); if (user == null) { - user = users.getUserById(requester, provider.getRealm()); + user = users.getUserByEmail(requester, provider.getRealm()); + } + + if (user == null) { + throw new NotFoundException("invalid_username_or_email"); } return user; diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/MyResourcesPage.java b/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/MyResourcesPage.java index 1165781d04..8a1096b711 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/MyResourcesPage.java +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/test/java/org/keycloak/testsuite/ui/account2/page/MyResourcesPage.java @@ -30,29 +30,27 @@ public class MyResourcesPage extends AbstractLoggedInPage { } public void clickShareButton(int row) { - final WebElement webElement = driver.findElement(By.id("ex-expand" + row)); - - //first button is share - webElement.findElements(By.tagName("button")).get(0).click(); + driver.findElement(By.id("share-" + row)).click(); } public void clickEditButton(int row) { - final WebElement webElement = driver.findElement(By.id("ex-expand" + row)); - - //first button share 2rd is the edit button - webElement.findElements(By.tagName("button")).get(1).click(); + final WebElement webElement = driver.findElement(By.id("action-menu-" + row)); + webElement.click(); + webElement.findElement(By.id("edit-" + row)).click(); } public void createShare(String userName) { driver.findElement(By.id("username")).sendKeys(userName); driver.findElement(By.id("add")).click(); - driver.findElement(By.id("remove_pf-random-id-1")).click(); + driver.findElement(By.id("pf-toggle-id-6")).click(); + driver.findElement(By.id("Scope A-1")).click(); + driver.findElement(By.id("pf-toggle-id-9")).click(); driver.findElement(By.id("done")).click(); } public void removeAllPermissions() { - driver.findElement(By.id("remove_pf-random-id-1")).click(); - driver.findElement(By.id("remove_pf-random-id-2")).click(); + driver.findElement(By.className("pf-c-select__toggle-clear")).click(); + driver.findElement(By.id("save-0")).click(); driver.findElement(By.id("done")).click(); } } diff --git a/themes/src/main/resources/theme/keycloak-preview/account/messages/messages_en.properties b/themes/src/main/resources/theme/keycloak-preview/account/messages/messages_en.properties index c92973410a..50ae335a37 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/messages/messages_en.properties +++ b/themes/src/main/resources/theme/keycloak-preview/account/messages/messages_en.properties @@ -43,7 +43,12 @@ firstPage=First Page resourceSharedWith=Resource is shared with {0} and=\ and {0} other users add=Add +share=Share +edit=Edit +unShare=Unshare shareSuccess=Resource successfully shared. +unShareSuccess=Resource successfully un-shared. +updateSuccess=Resource successfully updated. resourceAlreadyShared=Resource is already shared with this user. resourceNotShared=This resource is not shared. permissionRequests=Permission Requests diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/ContentAlert.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/ContentAlert.tsx index 996cdea727..92f0a62fac 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/ContentAlert.tsx +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/ContentAlert.tsx @@ -15,16 +15,17 @@ */ import * as React from 'react'; -import {Alert, AlertActionCloseButton} from '@patternfly/react-core'; -import {Msg} from '../widgets/Msg'; +import { Alert, AlertActionCloseButton, AlertGroup, AlertVariant } from '@patternfly/react-core'; +import { Msg } from '../widgets/Msg'; -interface ContentAlertProps {} +interface ContentAlertProps { } -type AlertVariant = 'success' | 'danger' | 'warning' | 'info'; interface ContentAlertState { - isVisible: boolean; - message: string; - variant: AlertVariant; + alerts: { + key: number; + message: string; + variant: AlertVariant; + }[]; } export class ContentAlert extends React.Component { private static instance: ContentAlert; @@ -32,7 +33,9 @@ export class ContentAlert extends React.Component { - this.setState({isVisible: false}); + private hideAlert = (key: number) => { + this.setState({ alerts: [...this.state.alerts.filter(el => el.key !== key)] }); } + private getUniqueId = () => (new Date().getTime()); + private postAlert = (variant: AlertVariant, message: string, params?: string[]) => { - this.setState({isVisible: true, - message: Msg.localize(message, params), - variant}); + const alerts = this.state.alerts; + const key = this.getUniqueId(); + alerts.push({ + key, + message: Msg.localize(message, params), + variant + }); + this.setState({ alerts }); - if (variant !== 'danger') { - setTimeout(() => this.setState({isVisible: false}), 5000); + if (variant !== AlertVariant.danger) { + setTimeout(() => this.hideAlert(key), 8000); } } public render(): React.ReactNode { return ( - - { this.state.isVisible && -
+ + {this.state.alerts.map(({ key, variant, message }) => ( } - > - {this.state.message} - -
- } -
+ isLiveRegion + variant={variant} + title={message} + action={ + this.hideAlert(key)} + /> + } + key={key} /> + ))} + ); } } \ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/EditTheResource.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/EditTheResource.tsx index 44da669ca0..0e75ccff03 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/EditTheResource.tsx +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/EditTheResource.tsx @@ -19,76 +19,76 @@ import * as React from 'react'; import { Button, Modal, - DataList, - DataListItemRow, - DataListItemCells, - DataListCell, - DataListItem, - ChipGroup, - ChipGroupToolbarItem, - Chip + Form, + FormGroup, + TextInput, + InputGroup } from '@patternfly/react-core'; - -import { EditAltIcon } from '@patternfly/react-icons'; +import { OkIcon } from '@patternfly/react-icons'; import { Resource, Permission, Scope } from './MyResourcesPage'; import { Msg } from '../../widgets/Msg'; -import AccountService, {HttpResponse} from '../../account-service/account.service'; +import AccountService from '../../account-service/account.service'; import { ContentAlert } from '../ContentAlert'; +import { PermissionSelect } from './PermissionSelect'; interface EditTheResourceProps { resource: Resource; permissions: Permission[]; - onClose: (resource: Resource, row: number) => void; - row: number; + onClose: () => void; + children: (toggle: () => void) => void; } interface EditTheResourceState { + changed: boolean[]; isOpen: boolean; } export class EditTheResource extends React.Component { - protected static defaultProps = { permissions: [], row: 0 }; + protected static defaultProps = { permissions: [] }; public constructor(props: EditTheResourceProps) { super(props); this.state = { + changed: [], isOpen: false, }; } private clearState(): void { - this.setState({ - }); + this.setState({}); } private handleToggleDialog = () => { if (this.state.isOpen) { this.setState({ isOpen: false }); + this.props.onClose(); } else { this.clearState(); this.setState({ isOpen: true }); } }; - async deletePermission(permission: Permission, scope: Scope): Promise { - permission.scopes.splice(permission.scopes.indexOf(scope), 1); + private updateChanged = (row: number) => { + const changed = this.state.changed; + changed[row] = !changed[row]; + this.setState({ changed }); + } + + async savePermission(permission: Permission): Promise { await AccountService.doPut(`/resources/${this.props.resource._id}/permissions`, [permission]); - ContentAlert.success(Msg.localize('shareSuccess')); - this.props.onClose(this.props.resource, this.props.row); + ContentAlert.success(Msg.localize('updateSuccess')); } public render(): React.ReactNode { return ( - + {this.props.children(this.handleToggleDialog)} , ]} > - - - - - , - - - , - ]} - /> - - {this.props.permissions.map((p, row) => { - return ( - - - - {p.username} - , - - - - { - p.scopes.length > 0 && p.scopes.map(scope => ( - this.deletePermission(p, scope)}> - {scope.displayName || scope} - - )) - } - - - - ]} +
+ {this.props.permissions.map((p, row) => ( + + + + + + + + new Scope(s))} + direction={row === this.props.permissions.length - 1 ? "up" : "down"} + onSelect={selection => { + p.scopes = selection.map(s => s.name); + this.updateChanged(row); + }} /> - - - ); - })} - + + + +
+
+ ))} +
); diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/MyResourcesPage.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/MyResourcesPage.tsx index cfb56fbfd4..7745fe171c 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/MyResourcesPage.tsx +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/MyResourcesPage.tsx @@ -75,7 +75,7 @@ export interface Permission { email?: string; firstName?: string; lastName?: string; - scopes: Scope[]; // this should be Scope[] - fix API + scopes: Scope[] | string[]; // this should be Scope[] - fix API username: string; } diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/PermissionRequest.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/PermissionRequest.tsx index fb779b30db..384e0a192f 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/PermissionRequest.tsx +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/PermissionRequest.tsx @@ -14,7 +14,21 @@ * limitations under the License. */ import * as React from 'react'; -import { Button, Modal, Text, Badge, DataListItem, DataList, TextVariants, DataListItemRow, DataListItemCells, DataListCell, Chip } from '@patternfly/react-core'; +import { + Button, + Modal, + Text, + Badge, + DataListItem, + DataList, + TextVariants, + DataListItemRow, + DataListItemCells, + DataListCell, + Chip, + Split, + SplitItem +} from '@patternfly/react-core'; import { UserCheckIcon } from '@patternfly/react-icons'; import AccountService, {HttpResponse} from '../../account-service/account.service'; @@ -59,7 +73,7 @@ export class PermissionRequest extends React.Component p.username === username)!.scopes; + const userScopes = permissions.find((p: Permission) => p.username === username)!.scopes as Scope[]; if (approve) { userScopes.push(...scopes); } @@ -124,15 +138,22 @@ export class PermissionRequest extends React.Component{shareRequest.email} , - {shareRequest.scopes.map((scope, j) => {scope})} + {(shareRequest.scopes as Scope[]).map((scope, j) => {scope})} , - - + + + + + + + + ]} /> diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/PermissionSelect.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/PermissionSelect.tsx new file mode 100644 index 0000000000..38f84c54e1 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/PermissionSelect.tsx @@ -0,0 +1,108 @@ +import * as React from 'react'; + +import { Select, SelectOption, SelectVariant, SelectOptionObject } from '@patternfly/react-core'; +import { Scope } from './MyResourcesPage'; + +interface PermissionSelectState { + selected: ScopeValue[]; + isExpanded: boolean; + scopes: JSX.Element[]; +} + +interface PermissionSelectProps { + scopes: Scope[]; + selected?: Scope[]; + direction?: 'up' | 'down'; + onSelect: (selected: Scope[]) => void; +} + +class ScopeValue implements SelectOptionObject { + value: Scope; + constructor(value: Scope) { + this.value = value; + } + + toString() { + return this.value.displayName ? this.value.displayName : this.value.name; + } + + compareTo(selectOption: Scope): boolean { + return selectOption.name === this.value.name; + } +} + +export class PermissionSelect extends React.Component { + constructor(props: PermissionSelectProps) { + super(props); + + let values: ScopeValue[] = []; + if (this.props.selected) { + values = this.props.selected!.map(s => new ScopeValue(s)) + } + + this.state = { + isExpanded: false, + selected: values, + scopes: this.props.scopes.map((option, index) => ( + s.compareTo(option)) || new ScopeValue(option)} /> + )) + }; + } + + private onSelect = (_event: React.MouseEvent | React.ChangeEvent, selection: ScopeValue): void => { + const { selected } = this.state; + const { onSelect } = this.props; + if (selected.includes(selection)) { + this.setState( + prevState => ({ selected: prevState.selected.filter(item => item !== selection) }), + () => onSelect(this.state.selected.map(sv => sv.value)) + ); + } else { + this.setState( + prevState => ({ selected: [...prevState.selected, selection] }), + () => onSelect(this.state.selected.map(sv => sv.value)) + ); + } + } + + private onToggle = (isExpanded: boolean) => { + this.setState({ + isExpanded + }); + } + + private clearSelection = () => { + this.setState({ + selected: [], + isExpanded: false + }); + this.props.onSelect([]); + }; + + render() { + const { isExpanded, selected } = this.state; + const titleId = 'permission-id'; + + return ( +
+ + +
+ ); + } +} \ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/ResourcesTable.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/ResourcesTable.tsx index ba1ff56f0d..403ef45f56 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/ResourcesTable.tsx +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/ResourcesTable.tsx @@ -26,24 +26,32 @@ import { DataListItemCells, Level, LevelItem, - Stack, - StackItem, - Button - } from '@patternfly/react-core'; + Button, + DataListAction, + DataListActionVisibility, + Dropdown, + DropdownPosition, + DropdownItem, + KebabToggle +} from '@patternfly/react-core'; +import { css } from '@patternfly/react-styles'; -import { Remove2Icon } from '@patternfly/react-icons'; +import { Remove2Icon, RepositoryIcon, ShareAltIcon, EditAltIcon } from '@patternfly/react-icons'; -import AccountService, {HttpResponse} from '../../account-service/account.service'; -import {PermissionRequest} from "./PermissionRequest"; -import {ShareTheResource} from "./ShareTheResource"; -import {Permission, Resource} from "./MyResourcesPage"; +import AccountService, { HttpResponse } from '../../account-service/account.service'; +import { PermissionRequest } from "./PermissionRequest"; +import { ShareTheResource } from "./ShareTheResource"; +import { Permission, Resource } from "./MyResourcesPage"; import { Msg } from '../../widgets/Msg'; import { ResourcesTableState, ResourcesTableProps, AbstractResourcesTable } from './AbstractResourceTable'; import { EditTheResource } from './EditTheResource'; import { ContentAlert } from '../ContentAlert'; +import EmptyMessageState from '../../widgets/EmptyMessageState'; export interface CollapsibleResourcesTableState extends ResourcesTableState { isRowOpen: boolean[]; + contextOpen: boolean[]; + isModalActive: boolean; } export class ResourcesTable extends AbstractResourcesTable { @@ -51,7 +59,9 @@ export class ResourcesTable extends AbstractResourcesTable(props.resources.data.length).fill(false), + isRowOpen: [], + contextOpen: [], + isModalActive: false, permissions: new Map() } } @@ -60,35 +70,50 @@ export class ResourcesTable extends AbstractResourcesTable { + if (this.state.isModalActive) return; + const data = this.props.resources.data; + const contextOpen = this.state.contextOpen; + contextOpen[row] = isOpen; + if (isOpen) { + const index = row > data.length ? row - data.length - 1 : row; + this.fetchPermissions(data[index], index); + } + this.setState({ contextOpen }); + } + private fetchPermissions(resource: Resource, row: number): void { AccountService.doGet(`/resources/${resource._id}/permissions`) - .then((response: HttpResponse) => { - const newPermissions: Map = new Map(this.state.permissions); - newPermissions.set(row, response.data || []); - this.setState({permissions: newPermissions}); - } - ); + .then((response: HttpResponse) => { + const newPermissions: Map = new Map(this.state.permissions); + newPermissions.set(row, response.data || []); + this.setState({ permissions: newPermissions }); + }); } - private removeShare(resource: Resource, row: number): void { + private removeShare(resource: Resource, row: number): Promise { const permissions = this.state.permissions.get(row)!.map(a => ({ username: a.username, scopes: [] })); - AccountService.doPut(`/resources/${resource._id}/permissions`, permissions) + return AccountService.doPut(`/resources/${resource._id}/permissions`, permissions) .then(() => { - ContentAlert.success(Msg.localize('shareSuccess')); - this.onToggle(row); + ContentAlert.success(Msg.localize('unShareSuccess')); }); } public render(): React.ReactNode { + if (this.props.resources.data.length === 0) { + return ( + + ); + } return ( // invisible toggle allows headings to line up properly - + - + , - + , - + , ]} /> - {(this.props.resources.data.length === 0) && } - {this.props.resources.data.map((resource: Resource, row: number) => { - return ( + {this.props.resources.data.map((resource: Resource, row: number) => ( this.onToggle(row)} + onClick={() => this.onToggle(row)} isExpanded={this.state.isRowOpen[row]} id={'resourceToggle-' + row} aria-controls="ex-expand1" @@ -124,7 +147,7 @@ export class ResourcesTable extends AbstractResourcesTable - + , {this.getClientName(resource.client)} @@ -139,6 +162,143 @@ export class ResourcesTable extends AbstractResourcesTable ]} /> + + this.setState({ isModalActive: true })} + toggle={ this.onContextToggle(row + this.props.resources.data.length + 1, isOpen)} />} + isOpen={this.state.contextOpen[row + this.props.resources.data.length + 1]} + dropdownItems={[ + { + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row + this.props.resources.data.length + 1, false); + }); + }} + > + { + (toggle: () => void) => ( + + + ) + } + , + { + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row + this.props.resources.data.length + 1, false); + }); + }} + > + { + (toggle: () => void) => ( + + + ) + } + , + { + this.removeShare(resource, row).then(() => + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row + this.props.resources.data.length + 1, false); + }) + ); + }} + > + + + ]} + /> + + + this.fetchPermissions(resource, row)} + > + { + (toggle: () => void) => ( + + ) + } + + this.onContextToggle(row, isOpen)} />} + onSelect={() => this.setState({ isModalActive: true })} + isOpen={this.state.contextOpen[row]} + dropdownItems={[ + { + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row, false); + this.fetchPermissions(resource, row); + }); + }} + > + { + (toggle: () => void) => ( + + + ) + } + , + { + this.removeShare(resource, row).then(() => + this.setState({ isModalActive: false }, () => { + this.onContextToggle(row, false); + }) + ); + }} + > + + + ]} + /> + + - - - - - {this.sharedWithUsersMessage(row)} - - - - - - - - - - - - - - - - - - - + + + {this.sharedWithUsersMessage(row)} + + - )})} + ))} ); } diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/ShareTheResource.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/ShareTheResource.tsx index f136b79396..e57b48430c 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/ShareTheResource.tsx +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/ShareTheResource.tsx @@ -31,19 +31,18 @@ import { TextInput } from '@patternfly/react-core'; -import { ShareAltIcon } from '@patternfly/react-icons'; - -import AccountService, {HttpResponse} from '../../account-service/account.service'; +import AccountService from '../../account-service/account.service'; import { Resource, Permission, Scope } from './MyResourcesPage'; import { Msg } from '../../widgets/Msg'; import {ContentAlert} from '../ContentAlert'; +import { PermissionSelect } from './PermissionSelect'; interface ShareTheResourceProps { resource: Resource; permissions: Permission[]; sharedWithUsersMsg: React.ReactNode; - onClose: (resource: Resource, row: number) => void; - row: number; + onClose: () => void; + children: (toggle: () => void) => void; } interface ShareTheResourceState { @@ -58,7 +57,7 @@ interface ShareTheResourceState { * @author Stan Silvert ssilvert@redhat.com (C) 2019 Red Hat Inc. */ export class ShareTheResource extends React.Component { - protected static defaultProps = {permissions: [], row: 0}; + protected static defaultProps = {permissions: []}; public constructor(props: ShareTheResourceProps) { super(props); @@ -100,13 +99,14 @@ export class ShareTheResource extends React.Component { ContentAlert.success('shareSuccess'); - this.props.onClose(this.props.resource, this.props.row); + this.props.onClose(); }) }; private handleToggleDialog = () => { if (this.state.isOpen) { this.setState({isOpen: false}); + this.props.onClose(); } else { this.clearState(); this.setState({isOpen: true}); @@ -136,21 +136,6 @@ export class ShareTheResource extends React.Component { - let newPermissionsSelected: Scope[] = this.state.permissionsSelected; - let newPermissionsUnSelected: Scope[] = this.state.permissionsUnSelected; - - if (newPermissionsSelected.includes(selectedPermission)) { - newPermissionsSelected = newPermissionsSelected.filter(permission => permission !== selectedPermission); - newPermissionsUnSelected.push(selectedPermission); - } else { - newPermissionsUnSelected = newPermissionsUnSelected.filter(permission => permission !== selectedPermission); - newPermissionsSelected.push(selectedPermission); - } - - this.setState({permissionsSelected: newPermissionsSelected, permissionsUnSelected: newPermissionsUnSelected}); - } - private isAddDisabled(): boolean { return this.state.usernameInput === '' || this.isAlreadyShared(); } @@ -168,12 +153,9 @@ export class ShareTheResource extends React.Component - + {this.props.children(this.handleToggleDialog)} @@ -233,30 +215,11 @@ export class ShareTheResource extends React.Component - {this.state.permissionsSelected.length < 1 && Select permissions below:} - - - {this.state.permissionsSelected.map((currentChip: Scope) => ( - this.handleSelectPermission(currentChip)}> - {currentChip.toString()} - - ))} - - - - - - - {this.state.permissionsUnSelected.map((currentChip: Scope) => ( - this.handleSelectPermission(currentChip)}> - {currentChip.toString()} - - ))} - - + this.setState({ permissionsSelected: selection })} + direction="up" + /> diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/SharedResourcesTable.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/SharedResourcesTable.tsx index 9f77666687..56346019f7 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/SharedResourcesTable.tsx +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/my-resources-page/SharedResourcesTable.tsx @@ -26,11 +26,13 @@ import { ChipGroupToolbarItem, Chip } from '@patternfly/react-core'; +import { RepositoryIcon } from '@patternfly/react-icons'; -import {PaginatedResources, Resource} from "./MyResourcesPage"; +import {PaginatedResources, Resource, Scope} from "./MyResourcesPage"; import { Msg } from '../../widgets/Msg'; import { AbstractResourcesTable, ResourcesTableState } from './AbstractResourceTable'; +import EmptyMessageState from '../../widgets/EmptyMessageState'; export interface ResourcesTableProps { resources: PaginatedResources; @@ -47,6 +49,11 @@ export class SharedResourcesTable extends AbstractResourcesTable + ); + } return ( @@ -65,7 +72,6 @@ export class SharedResourcesTable extends AbstractResourcesTable - {(this.props.resources.data.length === 0) && } {this.props.resources.data.map((resource: Resource, row: number) => ( @@ -96,7 +102,7 @@ export class SharedResourcesTable extends AbstractResourcesTable { - resource.shareRequests[0].scopes.map(scope => ( + (resource.shareRequests[0].scopes as Scope[]).map(scope => ( {scope.displayName || scope.name} diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/page-not-found/PageNotFound.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/page-not-found/PageNotFound.tsx index 9ee3f80a29..26fefe4326 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/page-not-found/PageNotFound.tsx +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/content/page-not-found/PageNotFound.tsx @@ -6,10 +6,10 @@ import * as React from 'react'; -import {EmptyState, EmptyStateBody, EmptyStateIcon, Title, TitleLevel} from '@patternfly/react-core'; import { WarningTriangleIcon } from '@patternfly/react-icons'; import {withRouter, RouteComponentProps} from 'react-router-dom'; import {Msg} from '../../widgets/Msg'; +import EmptyMessageState from '../../widgets/EmptyMessageState'; export interface PageNotFoundProps extends RouteComponentProps {} @@ -21,15 +21,9 @@ class PgNotFound extends React.Component { public render(): React.ReactNode { return ( - - - - <Msg msgKey='pageNotFound'/> - - - - - + + + ); } }; diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/app/widgets/EmptyMessageState.tsx b/themes/src/main/resources/theme/keycloak-preview/account/src/app/widgets/EmptyMessageState.tsx new file mode 100644 index 0000000000..a3aaa8e873 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/app/widgets/EmptyMessageState.tsx @@ -0,0 +1,53 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as React from 'react'; +import { + EmptyState, + EmptyStateVariant, + Title, + EmptyStateIcon, + TitleLevel, + EmptyStateBody, + IconProps, +} from '@patternfly/react-core' + +import { Msg } from './Msg'; + +export interface EmptyMessageStateProps { + icon: React.FunctionComponent; + messageKey: string; +} + +export default class EmptyMessageState extends React.Component { + constructor(props: EmptyMessageStateProps) { + super(props); + } + + render() { + return ( + + + + <Msg msgKey={this.props.messageKey} /> + + + {this.props.children} + + + ); + } +} \ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/package-lock.json b/themes/src/main/resources/theme/keycloak-preview/account/src/package-lock.json index 61423f7ed9..cca87a0f68 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/package-lock.json +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/package-lock.json @@ -2582,9 +2582,9 @@ } }, "@patternfly/react-styles": { - "version": "3.7.12", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.7.12.tgz", - "integrity": "sha512-vTKyC78oKlrS6VTQ3GPYevc17qgxj2Ono+SCDwoMyhUexPEyXRuZHLoZA1/MkJHvSCqJHGBageBAFcRq5wb0XQ==", + "version": "3.7.14", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.7.14.tgz", + "integrity": "sha512-NVwbPP9JroulfQgj0LOLWKP4DumArW8RrP1FB1lLOCuw13KkuAcFbLN9MSF8ZBwJ8syxGEdux5mDC3jPjsrQiw==", "requires": { "camel-case": "^3.0.0", "css": "^2.2.3", @@ -3211,9 +3211,9 @@ }, "dependencies": { "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "requires": { "path-parse": "^1.0.6" } diff --git a/themes/src/main/resources/theme/keycloak-preview/account/src/package.json b/themes/src/main/resources/theme/keycloak-preview/account/src/package.json index 4baa08eb2d..ad79c52ab9 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/src/package.json +++ b/themes/src/main/resources/theme/keycloak-preview/account/src/package.json @@ -17,6 +17,7 @@ "dependencies": { "@patternfly/react-core": "^3.153.3", "@patternfly/react-icons": "^3.15.16", + "@patternfly/react-styles": "^3.7.14", "react": "npm:@pika/react@^16.13.1", "react-dom": "npm:@pika/react-dom@^16.13.1", "react-router-dom": "^4.3.1"