From 7abeb2bae5ce670173076e12a17eabf4528b27af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20M=C3=B8ller=20Jensen?= <26099018+JesmoDev@users.noreply.github.com> Date: Tue, 25 Apr 2023 20:56:34 +1200 Subject: [PATCH 1/3] updated code snippet --- src/Umbraco.Web.UI.Client/.vscode/lit.code-snippets | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/.vscode/lit.code-snippets b/src/Umbraco.Web.UI.Client/.vscode/lit.code-snippets index fd01fd2b4c..38ffed71f5 100644 --- a/src/Umbraco.Web.UI.Client/.vscode/lit.code-snippets +++ b/src/Umbraco.Web.UI.Client/.vscode/lit.code-snippets @@ -4,18 +4,21 @@ "scope": "typescript", "body": [ "import { UUITextStyles } from '@umbraco-ui/uui-css';", - "import { css, html, LitElement } from 'lit';", + "import { css, html } from 'lit';", "import { customElement } from 'lit/decorators.js';", + "import { UmbLitElement } from '@umbraco-cms/internal/lit-element';", "", "@customElement('umb-${TM_FILENAME_BASE/(.*)\\..+$/$1/}')", - "export class Umb${TM_FILENAME_BASE/(.*)\\..+$/${1:/pascalcase}/}Element extends LitElement {", - "\tstatic styles = [UUITextStyles, css``];", - "", + "export class Umb${TM_FILENAME_BASE/(.*)\\..+$/${1:/pascalcase}/}Element extends UmbLitElement {", "\trender() {", "\t\treturn html`${0:umb-${TM_FILENAME_BASE/(.*)\\..+$/$1/}}`;", "\t}", + "", + "\tstatic styles = [UUITextStyles, css``];", "}", "", + "export default Umb${TM_FILENAME_BASE/(.*)\\..+$/${1:/pascalcase}/}Element", + "", "declare global {", "\tinterface HTMLElementTagNameMap {", "\t\t'umb-${TM_FILENAME_BASE/(.*)\\..+$/$1/}': Umb${TM_FILENAME_BASE/(.*)\\..+$/${1:/pascalcase}/}Element;", From c9701ace39390723bfaee1517fcab017f5ec3c87 Mon Sep 17 00:00:00 2001 From: JesmoDev <26099018+JesmoDev@users.noreply.github.com> Date: Wed, 26 Apr 2023 01:04:41 +1200 Subject: [PATCH 2/3] Feature: Wire Users backend - Part 1 (#659) * UsersResource format * wip update of repo and server * get user workspace to show * split up the section folder into the existing user and user group folders * cleanup * move stuff around * collection repository * added isSelected to the collection context * cleanup * user edit workspace kind of works again * fixed header * saving kinda works * add notes * cleaning up * clean clean * fixing build errors * fixing build errors * fix * build errors * fix import * comment out wrong import * remove store alias from collection class * move input-user files to user folder + temp render in property editor * align input naming * delete unused test file * wip align user picker modal code * fix buttons in edit workspace * property update method is now using types * User edit workspace cleanup * cleanup * render info items * remove methods from current user store * wire all detail methods with server * wire up user detail repository * clean up * remove debugger * delete test files * clean up * wire up create user modal * move header to element * wire up user filter * use correct type * use correct type * typescript temp fixes * add interface for filter model * temp typescript fixes * comment out * temp typescript fixes * remove unused * wip user invite * fix import * temp alert * remove unused * temp type cast * fix import * fix import * move user utils to user folder * fix tests --------- Co-authored-by: Mads Rasmussen --- .../backend-api/src/services/UserResource.ts | 584 ++-- .../workspace-view-collection.models.ts | 5 +- .../libs/modal/modal.interfaces.ts | 4 +- .../modal/token/user-picker-modal.token.ts | 19 +- .../libs/models/index.ts | 6 + .../collection-repository.interface.ts | 3 + .../collection-data-source.interface.ts | 7 + .../data-source/data-source.interface.ts | 4 +- .../libs/repository/data-source/index.ts | 1 + .../repository/detail-repository.interface.ts | 9 +- .../libs/repository/index.ts | 1 + src/Umbraco.Web.UI.Client/libs/utils/index.ts | 1 - .../sources/document-type.server.data.ts | 2 +- .../document-table-collection-view.element.ts | 17 +- .../sources/document.server.data.ts | 2 +- .../documents/repository/sources/index.ts | 2 +- .../media-grid-collection-view.element.ts | 13 +- .../media-table-collection-view.element.ts | 13 +- .../sources/media.detail.server.data.ts | 2 +- .../member-group.detail.server.data.ts | 2 +- .../sources/data-type.server.data.ts | 2 +- .../workspace/data-type-workspace.context.ts | 2 +- .../sources/language.server.data.ts | 2 +- .../sources/relation-type.server.data.ts | 3 +- .../shared/collection/collection.context.ts | 173 -- .../collection-selection-actions.element.ts | 30 +- .../collection/collection-toolbar.element.ts | 4 +- .../collection/collection.context.ts | 149 + .../collection/collection.element.ts | 6 +- .../dashboard-collection.element.ts | 10 +- .../dashboard-collection.stories.ts | 0 .../src/backoffice/shared/components/index.ts | 3 + .../input-user/input-user.element.ts | 97 - .../input-user/input-user.stories.ts | 30 - .../components/input-user/input-user.test.ts | 19 - .../workspace-view-collection.element.ts | 19 +- .../property-editor-ui-user-picker.element.ts | 7 +- .../sources/stylesheet.server.data.ts | 2 +- .../sources/template.detail.server.data.ts | 2 +- .../repository/dictionary.repository.ts | 1 + .../sources/dictionary.detail.server.data.ts | 2 +- .../users/current-user/current-user.store.ts | 27 +- .../current-user-modal.element.ts | 6 +- .../user-profile-app-profile.element.ts | 7 +- .../src/backoffice/users/index.ts | 6 +- .../backoffice/users/user-groups/manifests.ts | 3 +- .../user-groups/section-view/manifests.ts | 22 + .../section-view-user-groups.element.ts | 3 +- ...-group-table-name-column-layout.element.ts | 2 +- ...up-table-sections-column-layout.element.ts | 2 +- .../user-group-workspace-edit.element.ts | 38 +- .../workspace/user-group-workspace.test.ts | 20 - .../workspace-view-user-groups.element.ts | 13 +- .../users/user-section/manifests.ts | 41 +- .../user-section/section-users.element.ts | 34 - .../grid/workspace-view-users-grid.element.ts | 168 -- .../grid/workspace-view-users-grid.test.ts | 18 - .../views/users/section-view-users.element.ts | 148 - .../views/users/section-view-users.test.ts | 19 - .../workspace-view-users-overview.element.ts | 242 -- .../workspace-view-users-overview.test.ts | 19 - .../workspace-view-users-selection.element.ts | 84 - .../workspace-view-users-selection.test.ts | 19 - .../user-collection-header.element.ts | 185 ++ .../collection/user-collection.context.ts | 11 + .../collection/user-collection.element.ts | 68 + .../grid/user-grid-collection-view.element.ts | 135 + .../user-table-name-column-layout.element.ts | 2 +- ...user-table-status-column-layout.element.ts | 2 +- .../user-table-collection-view.element.ts} | 76 +- .../users/users/components/index.ts | 1 + .../user-input/user-input.context.ts | 10 + .../user-input/user-input.element.ts | 136 + .../user-input/user-input.stories.ts | 31 + .../delete/delete.action.ts | 24 + .../disable/disable.action.ts | 14 + .../enable/enable.action.ts | 14 + .../users/entity-bulk-actions/manifests.ts | 70 + .../set-group/set-group.action.ts | 24 + .../unlock/unlock.action.ts | 14 + .../src/backoffice/users/users/manifests.ts | 10 +- .../create-user/create-user-modal.element.ts | 58 +- .../create-user/create-user-modal.test.ts | 19 - .../invite-user/invite-user-modal.element.ts | 43 +- .../invite-user/invite-user-modal.test.ts | 19 - .../user-picker/user-picker-modal.element.ts | 149 +- .../user-picker/user-picker-modal.test.ts | 19 - .../sources/user-collection.server.data.ts | 32 + .../repository/sources/user.server.data.ts | 67 + .../users/users/repository/user.repository.ts | 124 +- .../users/users/repository/user.store.ts | 302 +- .../users/users/section-view/manifests.ts | 22 + .../users-section-view.element.ts | 86 + .../src/backoffice/users/users/types.ts | 41 + ... => user-workspace-action-save.element.ts} | 12 +- .../workspace/user-workspace-edit.element.ts | 323 +-- .../users/workspace/user-workspace.context.ts | 90 +- .../users/workspace/user-workspace.element.ts | 10 +- .../backoffice/users}/utils.test.ts | 12 +- .../utils => src/backoffice/users}/utils.ts | 16 +- .../src/core/mocks/data/users.data.ts | 2494 +---------------- .../src/core/mocks/domains/users.handlers.ts | 145 +- .../core/modal/modal-element-picker-base.ts | 2 +- 103 files changed, 2375 insertions(+), 4738 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/libs/repository/collection-repository.interface.ts create mode 100644 src/Umbraco.Web.UI.Client/libs/repository/data-source/collection-data-source.interface.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts rename src/Umbraco.Web.UI.Client/src/backoffice/shared/{ => components}/collection/collection-selection-actions.element.ts (84%) rename src/Umbraco.Web.UI.Client/src/backoffice/shared/{ => components}/collection/collection-toolbar.element.ts (97%) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection.context.ts rename src/Umbraco.Web.UI.Client/src/backoffice/shared/{ => components}/collection/collection.element.ts (98%) rename src/Umbraco.Web.UI.Client/src/backoffice/shared/{ => components}/collection/dashboards/dashboard-collection.element.ts (90%) rename src/Umbraco.Web.UI.Client/src/backoffice/shared/{ => components}/collection/dashboards/dashboard-collection.stories.ts (100%) delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.stories.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.test.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/section-view/manifests.ts rename src/Umbraco.Web.UI.Client/src/backoffice/users/{user-section/views/user-groups => user-groups/section-view}/section-view-user-groups.element.ts (88%) rename src/Umbraco.Web.UI.Client/src/backoffice/users/{user-section/views/user-groups => user-groups/workspace}/user-group-table-name-column-layout.element.ts (91%) rename src/Umbraco.Web.UI.Client/src/backoffice/users/{user-section/views/user-groups => user-groups/workspace}/user-group-table-sections-column-layout.element.ts (94%) delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.test.ts rename src/Umbraco.Web.UI.Client/src/backoffice/users/{user-section/views/user-groups => user-groups/workspace}/workspace-view-user-groups.element.ts (93%) delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/section-users.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.test.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.test.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.test.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.element.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.test.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection-header.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/grid/user-grid-collection-view.element.ts rename src/Umbraco.Web.UI.Client/src/backoffice/users/{user-section/views/users/list-view-layouts => users/collection/views}/table/column-layouts/name/user-table-name-column-layout.element.ts (95%) rename src/Umbraco.Web.UI.Client/src/backoffice/users/{user-section/views/users/list-view-layouts => users/collection/views}/table/column-layouts/status/user-table-status-column-layout.element.ts (90%) rename src/Umbraco.Web.UI.Client/src/backoffice/users/{user-section/views/users/list-view-layouts/table/workspace-view-users-table.element.ts => users/collection/views/table/user-table-collection-view.element.ts} (70%) create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/index.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.context.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.stories.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/delete/delete.action.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/disable/disable.action.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/enable/enable.action.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/set-group/set-group.action.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/unlock/unlock.action.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/create-user/create-user-modal.test.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/invite-user/invite-user-modal.test.ts delete mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.test.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/sources/user-collection.server.data.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/sources/user.server.data.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/section-view/manifests.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/section-view/users-section-view.element.ts create mode 100644 src/Umbraco.Web.UI.Client/src/backoffice/users/users/types.ts rename src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/{workspace-action-user-save.element.ts => user-workspace-action-save.element.ts} (84%) rename src/Umbraco.Web.UI.Client/{libs/utils => src/backoffice/users}/utils.test.ts (52%) rename src/Umbraco.Web.UI.Client/{libs/utils => src/backoffice/users}/utils.ts (55%) diff --git a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/UserResource.ts b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/UserResource.ts index 9ac2cfb564..9af279fb5c 100644 --- a/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/UserResource.ts +++ b/src/Umbraco.Web.UI.Client/libs/backend-api/src/services/UserResource.ts @@ -23,320 +23,302 @@ import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class UserResource { + /** + * @returns any Success + * @throws ApiError + */ + public static postUser({ + requestBody, + }: { + requestBody?: CreateUserRequestModel | InviteUserRequestModel; + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/user', + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Bad Request`, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static postUser({ - requestBody, - }: { - requestBody?: (CreateUserRequestModel | InviteUserRequestModel), - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/umbraco/management/api/v1/user', - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Bad Request`, - }, - }); - } + /** + * @returns PagedUserResponseModel Success + * @throws ApiError + */ + public static getUser({ + skip, + take = 100, + }: { + skip?: number; + take?: number; + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/user', + query: { + skip: skip, + take: take, + }, + }); + } - /** - * @returns PagedUserResponseModel Success - * @throws ApiError - */ - public static getUser({ - skip, - take = 100, - }: { - skip?: number, - take?: number, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/umbraco/management/api/v1/user', - query: { - 'skip': skip, - 'take': take, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static getUserById({ id }: { id: string }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/user/{id}', + path: { + id: id, + }, + errors: { + 404: `Not Found`, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static getUserById({ - id, - }: { - id: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/umbraco/management/api/v1/user/{id}', - path: { - 'id': id, - }, - errors: { - 404: `Not Found`, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static deleteUserById({ id }: { id: string }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/umbraco/management/api/v1/user/{id}', + path: { + id: id, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static deleteUserById({ - id, - }: { - id: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/umbraco/management/api/v1/user/{id}', - path: { - 'id': id, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static putUserById({ + id, + requestBody, + }: { + id: string; + requestBody?: UpdateUserRequestModel; + }): CancelablePromise { + return __request(OpenAPI, { + method: 'PUT', + url: '/umbraco/management/api/v1/user/{id}', + path: { + id: id, + }, + body: requestBody, + mediaType: 'application/json', + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static putUserById({ - id, - requestBody, - }: { - id: string, - requestBody?: UpdateUserRequestModel, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'PUT', - url: '/umbraco/management/api/v1/user/{id}', - path: { - 'id': id, - }, - body: requestBody, - mediaType: 'application/json', - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static deleteUserAvatarById({ id }: { id: string }): CancelablePromise { + return __request(OpenAPI, { + method: 'DELETE', + url: '/umbraco/management/api/v1/user/avatar/{id}', + path: { + id: id, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static deleteUserAvatarById({ - id, - }: { - id: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'DELETE', - url: '/umbraco/management/api/v1/user/avatar/{id}', - path: { - 'id': id, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static postUserAvatarById({ + id, + requestBody, + }: { + id: string; + requestBody?: SetAvatarRequestModel; + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/user/avatar/{id}', + path: { + id: id, + }, + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Bad Request`, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static postUserAvatarById({ - id, - requestBody, - }: { - id: string, - requestBody?: SetAvatarRequestModel, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/umbraco/management/api/v1/user/avatar/{id}', - path: { - 'id': id, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Bad Request`, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static postUserChangePasswordById({ + id, + requestBody, + }: { + id: string; + requestBody?: ChangePasswordUserRequestModel; + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/user/change-password/{id}', + path: { + id: id, + }, + body: requestBody, + mediaType: 'application/json', + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static postUserChangePasswordById({ - id, - requestBody, - }: { - id: string, - requestBody?: ChangePasswordUserRequestModel, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/umbraco/management/api/v1/user/change-password/{id}', - path: { - 'id': id, - }, - body: requestBody, - mediaType: 'application/json', - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static postUserDisable({ requestBody }: { requestBody?: DisableUserRequestModel }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/user/disable', + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Bad Request`, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static postUserDisable({ - requestBody, - }: { - requestBody?: DisableUserRequestModel, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/umbraco/management/api/v1/user/disable', - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Bad Request`, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static postUserEnable({ requestBody }: { requestBody?: EnableUserRequestModel }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/user/enable', + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Bad Request`, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static postUserEnable({ - requestBody, - }: { - requestBody?: EnableUserRequestModel, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/umbraco/management/api/v1/user/enable', - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Bad Request`, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static getUserFilter({ + skip, + take = 100, + orderBy, + orderDirection, + userGroupIds, + userStates, + filter = '', + }: { + skip?: number; + take?: number; + orderBy?: UserOrderModel; + orderDirection?: DirectionModel; + userGroupIds?: Array; + userStates?: Array; + filter?: string; + }): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/user/filter', + query: { + skip: skip, + take: take, + orderBy: orderBy, + orderDirection: orderDirection, + userGroupIds: userGroupIds, + userStates: userStates, + filter: filter, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static getUserFilter({ - skip, - take = 100, - orderBy, - orderDirection, - userGroupIds, - userStates, - filter = '', - }: { - skip?: number, - take?: number, - orderBy?: UserOrderModel, - orderDirection?: DirectionModel, - userGroupIds?: Array, - userStates?: Array, - filter?: string, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'GET', - url: '/umbraco/management/api/v1/user/filter', - query: { - 'skip': skip, - 'take': take, - 'orderBy': orderBy, - 'orderDirection': orderDirection, - 'userGroupIds': userGroupIds, - 'userStates': userStates, - 'filter': filter, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static postUserInvite({ requestBody }: { requestBody?: InviteUserRequestModel }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/user/invite', + body: requestBody, + mediaType: 'application/json', + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static postUserInvite({ - requestBody, - }: { - requestBody?: InviteUserRequestModel, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/umbraco/management/api/v1/user/invite', - body: requestBody, - mediaType: 'application/json', - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static getUserItem({ id }: { id?: Array }): CancelablePromise> { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/management/api/v1/user/item', + query: { + id: id, + }, + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static getUserItem({ - id, - }: { - id?: Array, - }): CancelablePromise> { - return __request(OpenAPI, { - method: 'GET', - url: '/umbraco/management/api/v1/user/item', - query: { - 'id': id, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static postUserSetUserGroups({ + requestBody, + }: { + requestBody?: UpdateUserGroupsOnUserRequestModel; + }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/user/set-user-groups', + body: requestBody, + mediaType: 'application/json', + }); + } - /** - * @returns any Success - * @throws ApiError - */ - public static postUserSetUserGroups({ - requestBody, - }: { - requestBody?: UpdateUserGroupsOnUserRequestModel, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/umbraco/management/api/v1/user/set-user-groups', - body: requestBody, - mediaType: 'application/json', - }); - } - - /** - * @returns any Success - * @throws ApiError - */ - public static postUserUnlock({ - requestBody, - }: { - requestBody?: UnlockUsersRequestModel, - }): CancelablePromise { - return __request(OpenAPI, { - method: 'POST', - url: '/umbraco/management/api/v1/user/unlock', - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Bad Request`, - }, - }); - } + /** + * @returns any Success + * @throws ApiError + */ + public static postUserUnlock({ requestBody }: { requestBody?: UnlockUsersRequestModel }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/user/unlock', + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Bad Request`, + }, + }); + } + /** + * @returns any Success + * @throws ApiError + */ + public static postUsersUnlock({ requestBody }: { requestBody?: UnlockUsersRequestModel }): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/umbraco/management/api/v1/users/unlock', + body: requestBody, + mediaType: 'application/json', + errors: { + 400: `Bad Request`, + }, + }); + } } diff --git a/src/Umbraco.Web.UI.Client/libs/extensions-registry/workspace-view-collection.models.ts b/src/Umbraco.Web.UI.Client/libs/extensions-registry/workspace-view-collection.models.ts index 309a2af11b..90627d068a 100644 --- a/src/Umbraco.Web.UI.Client/libs/extensions-registry/workspace-view-collection.models.ts +++ b/src/Umbraco.Web.UI.Client/libs/extensions-registry/workspace-view-collection.models.ts @@ -6,15 +6,12 @@ export interface ManifestWorkspaceViewCollection type: 'workspaceViewCollection'; meta: MetaEditorViewCollection; } - -// TODO: Get rid of store alias, when we are done migrating to repositories(remember to enforce repositoryAlias): export interface MetaEditorViewCollection { pathname: string; label: string; icon: string; entityType: string; - storeAlias?: string; - repositoryAlias?: string; + repositoryAlias: string; } export interface ConditionsEditorViewCollection { diff --git a/src/Umbraco.Web.UI.Client/libs/modal/modal.interfaces.ts b/src/Umbraco.Web.UI.Client/libs/modal/modal.interfaces.ts index 86ad653a1b..a31868a946 100644 --- a/src/Umbraco.Web.UI.Client/libs/modal/modal.interfaces.ts +++ b/src/Umbraco.Web.UI.Client/libs/modal/modal.interfaces.ts @@ -1,9 +1,9 @@ export interface UmbPickerModalData { multiple: boolean; selection: Array; - filter?: (language: T) => boolean; + filter?: (item: T) => boolean; } -export interface UmbPickerModalResult { +export interface UmbPickerModalResult { selection: Array; } diff --git a/src/Umbraco.Web.UI.Client/libs/modal/token/user-picker-modal.token.ts b/src/Umbraco.Web.UI.Client/libs/modal/token/user-picker-modal.token.ts index c1bf2c975a..43cced8222 100644 --- a/src/Umbraco.Web.UI.Client/libs/modal/token/user-picker-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/libs/modal/token/user-picker-modal.token.ts @@ -1,7 +1,16 @@ -import type { UserDetails } from '@umbraco-cms/backoffice/models'; import { UmbModalToken, UmbPickerModalData } from '@umbraco-cms/backoffice/modal'; +import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; -export const UMB_USER_PICKER_MODAL = new UmbModalToken>('Umb.Modal.UserPicker', { - type: 'sidebar', - size: 'small', -}); +export type UmbUserPickerModalData = UmbPickerModalData; + +export interface UmbUserPickerModalResult { + selection: Array; +} + +export const UMB_USER_PICKER_MODAL = new UmbModalToken( + 'Umb.Modal.UserPicker', + { + type: 'sidebar', + size: 'small', + } +); diff --git a/src/Umbraco.Web.UI.Client/libs/models/index.ts b/src/Umbraco.Web.UI.Client/libs/models/index.ts index 0d96ea3bff..d370269599 100644 --- a/src/Umbraco.Web.UI.Client/libs/models/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/models/index.ts @@ -108,3 +108,9 @@ export type PackageManifestResponse = UmbPackage[]; export type UmbPackageWithMigrationStatus = UmbPackage & { hasPendingMigrations: boolean; }; + +export interface UmbFilterModel { + skip?: number; + take?: number; + filter?: string; +} diff --git a/src/Umbraco.Web.UI.Client/libs/repository/collection-repository.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/collection-repository.interface.ts new file mode 100644 index 0000000000..a0bdaff15a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/repository/collection-repository.interface.ts @@ -0,0 +1,3 @@ +export interface UmbCollectionRepository { + requestCollection(filter?: any): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/libs/repository/data-source/collection-data-source.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/data-source/collection-data-source.interface.ts new file mode 100644 index 0000000000..92ac7cba1e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/libs/repository/data-source/collection-data-source.interface.ts @@ -0,0 +1,7 @@ +import type { UmbPagedData } from '../tree-repository.interface'; +import type { DataSourceResponse } from '../../repository'; + +export interface UmbCollectionDataSource> { + getCollection(): Promise>; + filterCollection(filter: any): Promise>; +} diff --git a/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source.interface.ts index 1721ce3e47..59cba2a69d 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/data-source/data-source.interface.ts @@ -1,9 +1,9 @@ import type { DataSourceResponse } from '@umbraco-cms/backoffice/repository'; -export interface UmbDataSource { +export interface UmbDataSource { createScaffold(parentId: string | null): Promise>; get(unique: string): Promise>; - insert(data: CreateRequestType): Promise; + insert(data: CreateRequestType): Promise>; update(unique: string, data: UpdateRequestType): Promise>; delete(unique: string): Promise; } diff --git a/src/Umbraco.Web.UI.Client/libs/repository/data-source/index.ts b/src/Umbraco.Web.UI.Client/libs/repository/data-source/index.ts index 08d765462a..d58b89b70e 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/data-source/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/data-source/index.ts @@ -5,3 +5,4 @@ export * from './tree-data-source.interface'; export * from './item-data-source.interface'; export * from './move-data-source.interface'; export * from './copy-data-source.interface'; +export * from './collection-data-source.interface'; diff --git a/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts b/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts index cb70ab8dd2..bea9ca37c4 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/detail-repository.interface.ts @@ -7,10 +7,15 @@ export interface UmbRepositoryResponse extends UmbRepositoryErrorResponse { data?: T; } -export interface UmbDetailRepository { +export interface UmbDetailRepository< + CreateRequestType = any, + CreateResponseType = any, + UpdateRequestType = any, + ResponseType = any +> { createScaffold(parentId: string | null): Promise>; requestById(id: string): Promise>; - create(data: CreateRequestType): Promise; + create(data: CreateRequestType): Promise>; save(id: string, data: UpdateRequestType): Promise; delete(id: string): Promise; } diff --git a/src/Umbraco.Web.UI.Client/libs/repository/index.ts b/src/Umbraco.Web.UI.Client/libs/repository/index.ts index ba407c2083..9efaa532e9 100644 --- a/src/Umbraco.Web.UI.Client/libs/repository/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/repository/index.ts @@ -2,6 +2,7 @@ export * from './data-source'; export * from './detail-repository.interface'; export * from './tree-repository.interface'; export * from './folder-repository.interface'; +export * from './collection-repository.interface'; export * from './item-repository.interface'; export * from './move-repository.interface'; export * from './copy-repository.interface'; diff --git a/src/Umbraco.Web.UI.Client/libs/utils/index.ts b/src/Umbraco.Web.UI.Client/libs/utils/index.ts index d16b3e3261..57b8109790 100644 --- a/src/Umbraco.Web.UI.Client/libs/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/libs/utils/index.ts @@ -1,4 +1,3 @@ -export * from './utils'; export * from './umbraco-path'; export * from './udi-service'; export * from './generate-guid'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/sources/document-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/sources/document-type.server.data.ts index 5a63069de6..f8f99c484a 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/sources/document-type.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/repository/sources/document-type.server.data.ts @@ -9,7 +9,7 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; * @class UmbDocumentTypeServerDataSource * @implements {RepositoryDetailDataSource} */ -export class UmbDocumentTypeServerDataSource implements UmbDataSource { +export class UmbDocumentTypeServerDataSource implements UmbDataSource { #host: UmbControllerHostElement; /** diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/collection/views/table/document-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/collection/views/table/document-table-collection-view.element.ts index 4fc874eaf6..39ab0f0a8d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/collection/views/table/document-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/collection/views/table/document-table-collection-view.element.ts @@ -1,10 +1,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { - UmbCollectionContext, - UMB_COLLECTION_CONTEXT_TOKEN, -} from '../../../../../shared/collection/collection.context'; + import { UmbTableColumn, UmbTableConfig, @@ -14,6 +11,10 @@ import { UmbTableOrderedEvent, UmbTableSelectedEvent, } from '../../../../../shared/components/table'; +import { + UMB_COLLECTION_CONTEXT_TOKEN, + UmbCollectionContext, +} from '../../../../../shared/components/collection/collection.context'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { DocumentTreeItemResponseModel, EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @@ -23,8 +24,6 @@ type EntityType = DocumentTreeItemResponseModel; @customElement('umb-document-table-collection-view') export class UmbDocumentTableCollectionViewElement extends UmbLitElement { - - @state() private _items?: Array; @@ -55,7 +54,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { @state() private _selection: Array = []; - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; constructor() { super(); @@ -68,7 +67,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { private _observeCollectionContext() { if (!this._collectionContext) return; - this.observe(this._collectionContext.data, (items) => { + this.observe(this._collectionContext.items, (items) => { this._items = items; this._createTableItems(this._items); }); @@ -134,7 +133,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { @ordered="${this._handleOrdering}"> `; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/document.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/document.server.data.ts index 7f93a35b7e..1f6ccfde12 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/document.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/document.server.data.ts @@ -17,7 +17,7 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; * @implements {RepositoryDetailDataSource} */ export class UmbDocumentServerDataSource - implements UmbDataSource + implements UmbDataSource { #host: UmbControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/index.ts index 8cbe1ec6d5..b73f71a4a6 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/documents/repository/sources/index.ts @@ -1,7 +1,7 @@ import type { DocumentResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbDataSource, DataSourceResponse } from '@umbraco-cms/backoffice/repository'; -export interface UmbDocumentDataSource extends UmbDataSource { +export interface UmbDocumentDataSource extends UmbDataSource { createScaffold(documentTypeKey: string): Promise>; trash(id: string): Promise>; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/collection-view/media-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/collection-view/media-grid-collection-view.element.ts index 889e595f7d..195bfcadd4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/collection-view/media-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/collection-view/media-grid-collection-view.element.ts @@ -2,22 +2,23 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; -import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from '../../../shared/collection/collection.context'; +import { + UmbCollectionContext, + UMB_COLLECTION_CONTEXT_TOKEN, +} from '../../../shared/components/collection/collection.context'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; // TODO: this should be a lib import @customElement('umb-media-grid-collection-view') export class UmbMediaGridCollectionViewElement extends UmbLitElement { - - @state() private _mediaItems?: Array; @state() private _selection: Array = []; - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; constructor() { super(); @@ -53,7 +54,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { private _observeCollectionContext() { if (!this._collectionContext) return; - this.observe(this._collectionContext.data, (mediaItems) => { + this.observe(this._collectionContext.items, (mediaItems) => { this._mediaItems = [...mediaItems].sort((a, b) => (a.hasChildren === b.hasChildren ? 0 : a ? -1 : 1)); }); @@ -120,7 +121,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { `; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/collection-view/media-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/collection-view/media-table-collection-view.element.ts index 31d502c771..ae71670acf 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/collection-view/media-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/collection-view/media-table-collection-view.element.ts @@ -11,14 +11,15 @@ import type { UmbTableSelectedEvent, } from '../../../shared/components/table'; import type { MediaDetails } from '../'; -import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from '../../../shared/collection/collection.context'; +import { + UmbCollectionContext, + UMB_COLLECTION_CONTEXT_TOKEN, +} from '../../../shared/components/collection/collection.context'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-media-table-collection-view') export class UmbMediaTableCollectionViewElement extends UmbLitElement { - - @state() private _mediaItems?: Array; @@ -41,7 +42,7 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { @state() private _selection: Array = []; - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; constructor() { super(); @@ -54,7 +55,7 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { private _observeCollectionContext() { if (!this._collectionContext) return; - this.observe(this._collectionContext.data, (nodes) => { + this.observe(this._collectionContext.items, (nodes) => { this._mediaItems = nodes; this._createTableItems(this._mediaItems); }); @@ -114,7 +115,7 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { @ordered="${this._handleOrdering}"> `; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/sources/media.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/sources/media.detail.server.data.ts index eadce6d3e8..c6618f5c5f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/sources/media.detail.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/media/media/repository/sources/media.detail.server.data.ts @@ -11,7 +11,7 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; * @implements {TemplateDetailDataSource} */ export class UmbMediaDetailServerDataSource - implements UmbDataSource + implements UmbDataSource { #host: UmbControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/sources/member-group.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/sources/member-group.detail.server.data.ts index c70bf9b4b4..89c7ee4775 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/sources/member-group.detail.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/members/member-groups/repository/sources/member-group.detail.server.data.ts @@ -10,7 +10,7 @@ import { UmbDataSource } from '@umbraco-cms/backoffice/repository'; * @implements {MemberGroupDetailDataSource} */ // TODO => Provide type when it is available -export class UmbMemberGroupDetailServerDataSource implements UmbDataSource { +export class UmbMemberGroupDetailServerDataSource implements UmbDataSource { #host: UmbControllerHostElement; constructor(host: UmbControllerHostElement) { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts index 11c6c99093..66a5f9f560 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/repository/sources/data-type.server.data.ts @@ -17,7 +17,7 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; * @implements {RepositoryDetailDataSource} */ export class UmbDataTypeServerDataSource - implements UmbDataSource + implements UmbDataSource { #host: UmbControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts index 15a5714fc1..33d7922fc0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/data-types/workspace/data-type-workspace.context.ts @@ -1,7 +1,7 @@ import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; import { UmbDataTypeRepository } from '../repository/data-type.repository'; import { UmbEntityWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; -import type { CreateDataTypeRequestModel, DataTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { DataTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { appendToFrozenArray, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts index ab9a3016f2..82906d0117 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/languages/repository/sources/language.server.data.ts @@ -14,7 +14,7 @@ import type { UmbDataSource } from '@umbraco-cms/backoffice/repository'; * @implements {RepositoryDetailDataSource} */ export class UmbLanguageServerDataSource - implements UmbDataSource + implements UmbDataSource { #host: UmbControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/sources/relation-type.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/sources/relation-type.server.data.ts index cd685f438f..5777a4268d 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/sources/relation-type.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/relation-types/repository/sources/relation-type.server.data.ts @@ -15,7 +15,8 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; * @implements {RepositoryDetailDataSource} */ export class UmbRelationTypeServerDataSource - implements UmbDataSource + implements + UmbDataSource { #host: UmbControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts deleted file mode 100644 index 4f2512206c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.context.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Observable } from 'rxjs'; -import type { EntityTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; -import { UmbContextToken, UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; -import { UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; -import { umbExtensionsRegistry, createExtensionClass } from '@umbraco-cms/backoffice/extensions-api'; -import { UmbTreeRepository } from '@umbraco-cms/backoffice/repository'; - -// TODO: Clean up the need for store as Media has switched to use Repositories(repository). -export class UmbCollectionContext { - private _host: UmbControllerHostElement; - private _entityType: string | null; - private _entityId: string | null; - - #repository?: UmbTreeRepository; - - private _store?: any; - protected _dataObserver?: UmbObserverController; - - #data = new UmbArrayState(>[]); - public readonly data = this.#data.asObservable(); - - #selection = new UmbArrayState(>[]); - public readonly selection = this.#selection.asObservable(); - - /* - TODO: - private _search = new UmbStringState(''); - public readonly search = this._search.asObservable(); - */ - - constructor( - host: UmbControllerHostElement, - entityType: string | null, - entityId: string | null, - storeAlias?: string, - repositoryAlias?: string - ) { - this._entityType = entityType; - this._host = host; - this._entityId = entityId; - - if (storeAlias) { - new UmbContextConsumerController(this._host, storeAlias, (_instance) => { - this._store = _instance; - if (!this._store) { - // TODO: if we keep the type assumption of _store existing, then we should here make sure to break the application in a good way. - return; - } - this._onStoreSubscription(); - }); - } else if (repositoryAlias) { - new UmbObserverController( - this._host, - umbExtensionsRegistry.getByTypeAndAlias('repository', repositoryAlias), - async (repositoryManifest) => { - if (repositoryManifest) { - // TODO: use the right interface here, we might need a collection repository interface. - const result = await createExtensionClass(repositoryManifest, [this._host]); - this.#repository = result; - this._onRepositoryReady(); - } - } - ); - } - } - - /* - public getData() { - return this.#data.getValue(); - } - */ - - /* - public update(data: Partial) { - this._data.next({ ...this.getData(), ...data }); - } - */ - - public getEntityType() { - return this._entityType; - } - - protected _onStoreSubscription(): void { - if (!this._store) { - return; - } - - this._dataObserver?.destroy(); - - if (this._entityId) { - this._dataObserver = new UmbObserverController( - this._host, - this._store.getTreeItemChildren(this._entityId), - (nodes) => { - if (nodes) { - this.#data.next(nodes); - } - } - ); - } else { - this._dataObserver = new UmbObserverController(this._host, this._store.getTreeRoot(), (nodes) => { - if (nodes) { - this.#data.next(nodes); - } - }); - } - } - - protected async _onRepositoryReady() { - if (!this.#repository) { - return; - } - - this._dataObserver?.destroy(); - - if (this._entityId) { - // TODO: we should be able to get an observable from this call. either return a observable or a asObservable() method. - const observable = (await this.#repository.requestTreeItemsOf(this._entityId)).asObservable?.(); - - if (observable) { - this._dataObserver = new UmbObserverController(this._host, observable as Observable, (nodes) => { - if (nodes) { - this.#data.next(nodes); - } - }); - } - } else { - const observable = (await this.#repository.requestRootTreeItems()).asObservable?.(); - - if (observable) { - this._dataObserver = new UmbObserverController(this._host, observable as Observable, (nodes) => { - if (nodes) { - this.#data.next(nodes); - } - }); - } - } - } - - /* - TODO: - public setSearch(value: string) { - if (!value) value = ''; - - this._search.next(value); - } - */ - - public setSelection(value: Array) { - if (!value) return; - this.#selection.next(value); - } - - public clearSelection() { - this.#selection.next([]); - } - - public select(id: string) { - this.#selection.appendOne(id); - } - - public deselect(id: string) { - this.#selection.filter((k) => k !== id); - } - - // TODO: how can we make sure to call this. - public destroy(): void { - this.#data.unsubscribe(); - } -} - -export const UMB_COLLECTION_CONTEXT_TOKEN = new UmbContextToken>('UmbCollectionContext'); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-selection-actions.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection-selection-actions.element.ts similarity index 84% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-selection-actions.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection-selection-actions.element.ts index 713f229730..8ed0457f26 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-selection-actions.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection-selection-actions.element.ts @@ -1,21 +1,18 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, nothing } from 'lit'; -import { customElement, property, state } from 'lit/decorators.js'; +import { css, html } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; import { map } from 'rxjs'; -import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from './collection.context'; +import { UMB_COLLECTION_CONTEXT_TOKEN, UmbCollectionContext } from './collection.context'; import type { ManifestEntityBulkAction } from '@umbraco-cms/backoffice/extensions-registry'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; import { UmbExecutedEvent } from '@umbraco-cms/backoffice/events'; -import '../components/entity-bulk-action/entity-bulk-action.element'; +import '../entity-bulk-action/entity-bulk-action.element'; @customElement('umb-collection-selection-actions') export class UmbCollectionSelectionActionsElement extends UmbLitElement { - - - @property() - public entityType: string | null = null; + #entityType?: string; @state() private _nodesLength = 0; @@ -26,16 +23,19 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement { @state() private _entityBulkActions: Array = []; - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; private _selection: Array = []; constructor() { super(); this.consumeContext(UMB_COLLECTION_CONTEXT_TOKEN, (instance) => { this._collectionContext = instance; - this.entityType = instance.getEntityType(); this._observeCollectionContext(); - this.#observeEntityBulkActions(); + + if (instance.getEntityType()) { + this.#entityType = instance.getEntityType() ?? undefined; + this.#observeEntityBulkActions(); + } }); } @@ -53,7 +53,7 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement { if (!this._collectionContext) return; // TODO: Make sure it only updates on length change. - this.observe(this._collectionContext.data, (mediaItems) => { + this.observe(this._collectionContext.items, (mediaItems) => { this._nodesLength = mediaItems.length; }); @@ -72,7 +72,7 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement { this.observe( umbExtensionsRegistry.extensionsOfType('entityBulkAction').pipe( map((extensions) => { - return extensions.filter((extension) => extension.conditions.entityType === this.entityType); + return extensions.filter((extension) => extension.conditions.entityType === this.#entityType); }) ), (bulkActions) => { @@ -87,7 +87,7 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement { } render() { - if (this._selectionLength === 0) return nothing; + // if (this._selectionLength === 0) return nothing; return html`
`; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-toolbar.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection-toolbar.element.ts similarity index 97% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-toolbar.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection-toolbar.element.ts index cf405aa30b..dc2ce3f517 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection-toolbar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection-toolbar.element.ts @@ -2,8 +2,8 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { map } from 'rxjs'; -import { TooltipMenuItem } from '../components/tooltip-menu'; -import '../components/tooltip-menu/tooltip-menu.element'; +import { TooltipMenuItem } from '../tooltip-menu'; +import '../tooltip-menu/tooltip-menu.element'; import type { ManifestCollectionView } from '@umbraco-cms/backoffice/extensions-registry'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection.context.ts new file mode 100644 index 0000000000..6f0ae47adc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection.context.ts @@ -0,0 +1,149 @@ +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; +import { + UmbArrayState, + UmbNumberState, + UmbObjectState, + UmbObserverController, +} from '@umbraco-cms/backoffice/observable-api'; +import { umbExtensionsRegistry, createExtensionClass } from '@umbraco-cms/backoffice/extensions-api'; +import { UmbCollectionRepository } from '@umbraco-cms/backoffice/repository'; +import type { UmbFilterModel } from '@umbraco-cms/backoffice/models'; + +// TODO: Clean up the need for store as Media has switched to use Repositories(repository). +export class UmbCollectionContext { + private _host: UmbControllerHostElement; + private _entityType: string | null; + + protected _dataObserver?: UmbObserverController; + + #items = new UmbArrayState([]); + public readonly items = this.#items.asObservable(); + + #total = new UmbNumberState(0); + public readonly total = this.#total.asObservable(); + + #selection = new UmbArrayState([]); + public readonly selection = this.#selection.asObservable(); + + #filter = new UmbObjectState({}); + public readonly filter = this.#filter.asObservable(); + + repository?: UmbCollectionRepository; + + /* + TODO: + private _search = new StringState(''); + public readonly search = this._search.asObservable(); + */ + + constructor(host: UmbControllerHostElement, entityType: string | null, repositoryAlias: string) { + this._entityType = entityType; + this._host = host; + + new UmbObserverController( + this._host, + umbExtensionsRegistry.getByTypeAndAlias('repository', repositoryAlias), + async (repositoryManifest) => { + if (repositoryManifest) { + const result = await createExtensionClass(repositoryManifest, [this._host]); + this.repository = result; + this._onRepositoryReady(); + } + } + ); + } + + public isSelected(id: string) { + return this.#selection.getValue().includes(id); + } + + public setSelection(value: Array) { + if (!value) return; + this.#selection.next(value); + } + + public clearSelection() { + this.#selection.next([]); + } + + public select(id: string) { + this.#selection.appendOne(id); + } + + public deselect(id: string) { + this.#selection.filter((k) => k !== id); + } + + // TODO: how can we make sure to call this. + public destroy(): void { + this.#items.unsubscribe(); + } + + public getEntityType() { + return this._entityType; + } + + /* + public getData() { + return this.#data.getValue(); + } + */ + + /* + public update(data: Partial) { + this._data.next({ ...this.getData(), ...data }); + } + */ + + // protected _onStoreSubscription(): void { + // if (!this._store) { + // return; + // } + + // this._dataObserver?.destroy(); + + // if (this._entityId) { + // this._dataObserver = new UmbObserverController( + // this._host, + // this._store.getTreeItemChildren(this._entityId), + // (nodes) => { + // if (nodes) { + // this.#data.next(nodes); + // } + // } + // ); + // } else { + // this._dataObserver = new UmbObserverController(this._host, this._store.getTreeRoot(), (nodes) => { + // if (nodes) { + // this.#data.next(nodes); + // } + // }); + // } + // } + + protected async _onRepositoryReady() { + if (!this.repository) return; + this.requestCollection(); + } + + public async requestCollection() { + if (!this.repository) return; + + const filter = this.#filter.getValue(); + const { data } = await this.repository.requestCollection(filter); + + if (data) { + this.#total.next(data.total); + this.#items.next(data.items); + } + } + + // TODO: find better name + setFilter(filter: Partial) { + this.#filter.next({ ...this.#filter.getValue(), ...filter }); + this.requestCollection(); + } +} + +export const UMB_COLLECTION_CONTEXT_TOKEN = new UmbContextToken>('UmbCollectionContext'); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection.element.ts similarity index 98% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection.element.ts index ed2235102c..fab5c9a23f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/collection.element.ts @@ -13,15 +13,13 @@ import type { IRoute } from '@umbraco-cms/backoffice/router'; @customElement('umb-collection') export class UmbCollectionElement extends UmbLitElement { - - @state() private _routes: Array = []; @state() private _selection?: Array | null; - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; private _entityType!: string; @property({ type: String, attribute: 'entity-type' }) @@ -96,7 +94,7 @@ export class UmbCollectionElement extends UmbLitElement { `; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/dashboards/dashboard-collection.element.ts similarity index 90% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/dashboards/dashboard-collection.element.ts index 82ca1ad616..638836c4a1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/dashboards/dashboard-collection.element.ts @@ -2,7 +2,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; -import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN } from '../../../shared/collection/collection.context'; +import { UMB_COLLECTION_CONTEXT_TOKEN, UmbCollectionContext } from '../collection.context'; import type { ManifestDashboardCollection } from '@umbraco-cms/backoffice/extensions-registry'; import type { FolderTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @@ -11,10 +11,8 @@ import '../collection.element'; @customElement('umb-dashboard-collection') export class UmbDashboardCollectionElement extends UmbLitElement { - - // TODO: Use the right type here: - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; public manifest!: ManifestDashboardCollection; @@ -27,7 +25,7 @@ export class UmbDashboardCollectionElement extends UmbLitElement { if (!this._collectionContext) { const repositoryAlias = this.manifest.meta.repositoryAlias; this._entityType = this.manifest.conditions.entityType; - this._collectionContext = new UmbCollectionContext(this, this._entityType, null, '', repositoryAlias); + this._collectionContext = new UmbCollectionContext(this, this._entityType, repositoryAlias); this.provideContext(UMB_COLLECTION_CONTEXT_TOKEN, this._collectionContext); } } @@ -35,7 +33,7 @@ export class UmbDashboardCollectionElement extends UmbLitElement { render() { return html``; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/dashboards/dashboard-collection.stories.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/backoffice/shared/collection/dashboards/dashboard-collection.stories.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/shared/components/collection/dashboards/dashboard-collection.stories.ts diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts index e258078e0f..18c7a4e139 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/index.ts @@ -12,6 +12,9 @@ import './backoffice-frame/backoffice-modal-container.element'; import './backoffice-frame/backoffice-notification-container.element'; import './button-with-dropdown/button-with-dropdown.element'; import './code-block/code-block.element'; +import './collection/collection.element'; +import './collection/collection-toolbar.element'; +import './collection/collection-selection-actions.element'; import './debug/debug.element'; import './donut-chart'; import './dropdown/dropdown.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.element.ts deleted file mode 100644 index 8a51a0ac85..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.element.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { UUITextStyles } from '@umbraco-ui/uui-css'; -import { css, html, nothing, PropertyValueMap } from 'lit'; -import { customElement, state } from 'lit/decorators.js'; -import { UmbInputListBaseElement } from '../input-list-base/input-list-base'; -import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../../users/users/repository/user.store'; -import { UMB_USER_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; -import type { UserEntity } from '@umbraco-cms/backoffice/models'; - -@customElement('umb-input-user') -export class UmbPickerUserElement extends UmbInputListBaseElement { - - - @state() - private _users: Array = []; - - private _userStore?: UmbUserStore; - - connectedCallback(): void { - super.connectedCallback(); - this.pickerToken = UMB_USER_PICKER_MODAL; - this.consumeContext(UMB_USER_STORE_CONTEXT_TOKEN, (userStore) => { - this._userStore = userStore; - this._observeUser(); - }); - } - - protected updated(_changedProperties: PropertyValueMap | Map): void { - super.updated(_changedProperties); - if (_changedProperties.has('value')) { - this._observeUser(); // TODO: This works, but it makes the value change twice. - } - } - - private _observeUser() { - if (!this._userStore) return; - - // TODO: Fix type mismatch: - this.observe>(this._userStore.getByKeys(this.value), (users) => { - this._users = users; - }); - } - - selectionUpdated() { - this._observeUser(); - this.dispatchEvent(new CustomEvent('change', { bubbles: true, composed: true })); - } - - private _renderUserList() { - if (this._users.length === 0) return nothing; - - return html`
- ${this._users.map( - (user) => html` -
- -
${user.name}
- this.removeFromSelection(user.id)} label="remove" color="danger"> -
- ` - )} -
`; - } - - renderContent() { - return html`${this._renderUserList()}`; - } - - static styles = [ - UUITextStyles, - css` - :host { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-4); - } - #user-list { - display: flex; - flex-direction: column; - gap: var(--uui-size-space-4); - } - .user { - display: flex; - align-items: center; - gap: var(--uui-size-space-2); - } - .user uui-button { - margin-left: auto; - } - `, - ]; -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-input-user': UmbPickerUserElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.stories.ts deleted file mode 100644 index e1f907ddd1..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.stories.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Meta, StoryObj } from '@storybook/web-components'; -import './input-user.element'; -import type { UmbPickerUserElement } from './input-user.element'; - -const meta: Meta = { - title: 'Components/Inputs/User', - component: 'umb-input-user', - argTypes: { - modalType: { - control: 'inline-radio', - options: ['dialog', 'sidebar'], - defaultValue: 'sidebar', - description: 'The type of modal to use when selecting users', - }, - modalSize:{ - control: 'select', - options: ['small', 'medium', 'large', 'full'], - defaultValue: 'small', - description: 'The size of the modal to use when selecting users, only applicable to sidebar not dialog', - } - } -}; - -export default meta; -type Story = StoryObj; - -export const Overview: Story = { - args: { - } -}; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.test.ts deleted file mode 100644 index 7d85d80d5f..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-user/input-user.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -//TODO: Test has been commented out while we figure out how to setup import maps for the test environment -// import { UmbPickerUserElement } from './picker-user.element'; -// import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// describe('UmbPickerUserElement', () => { -// let element: UmbPickerUserElement; -// beforeEach(async () => { -// element = await fixture(html``); -// }); - -// it('is defined with its own instance', () => { -// expect(element).to.be.instanceOf(UmbPickerUserElement); -// }); - -// it('passes the a11y audit', async () => { -// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); -// }); -// }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts index 08558273e5..885b1ffb82 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-content/views/collection/workspace-view-collection.element.ts @@ -5,24 +5,23 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { UmbCollectionContext, UMB_COLLECTION_CONTEXT_TOKEN, -} from '../../../../../../shared/collection/collection.context'; +} from '../../../../../../shared/components/collection/collection.context'; -import '../../../../../../shared/collection/dashboards/dashboard-collection.element'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { FolderTreeItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; import type { ManifestWorkspaceViewCollection } from '@umbraco-cms/backoffice/extensions-registry'; import { UMB_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/context-api'; +import '../../../../../../shared/components/collection/dashboards/dashboard-collection.element'; + @customElement('umb-workspace-view-collection') export class UmbWorkspaceViewCollectionElement extends UmbLitElement { - - public manifest!: ManifestWorkspaceViewCollection; private _workspaceContext?: typeof UMB_ENTITY_WORKSPACE_CONTEXT.TYPE; // TODO: add type for the collection context. - private _collectionContext?: UmbCollectionContext; + private _collectionContext?: UmbCollectionContext; constructor() { super(); @@ -40,13 +39,7 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement { if (entityId != null && entityType != null) { const manifestMeta = this.manifest.meta; - this._collectionContext = new UmbCollectionContext( - this, - entityType, - entityId, - manifestMeta.storeAlias, - manifestMeta.repositoryAlias - ); + this._collectionContext = new UmbCollectionContext(this, entityType, manifestMeta.repositoryAlias); this.provideContext(UMB_COLLECTION_CONTEXT_TOKEN, this._collectionContext); } } @@ -54,7 +47,7 @@ export class UmbWorkspaceViewCollectionElement extends UmbLitElement { render() { return html``; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/user-picker/property-editor-ui-user-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/user-picker/property-editor-ui-user-picker.element.ts index f06c25f5bc..ee7f67b592 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/user-picker/property-editor-ui-user-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/property-editors/uis/user-picker/property-editor-ui-user-picker.element.ts @@ -9,18 +9,17 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; */ @customElement('umb-property-editor-ui-user-picker') export class UmbPropertyEditorUIUserPickerElement extends UmbLitElement implements UmbPropertyEditorElement { - - @property() value = ''; @property({ type: Array, attribute: false }) public config = []; + // TODO: implement config render() { - return html`
umb-property-editor-ui-user-picker
`; + return html` `; } - + static styles = [UUITextStyles]; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/templating/stylesheets/repository/sources/stylesheet.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/templating/stylesheets/repository/sources/stylesheet.server.data.ts index 6ce1917236..8656c35eea 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/templating/stylesheets/repository/sources/stylesheet.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/templating/stylesheets/repository/sources/stylesheet.server.data.ts @@ -8,7 +8,7 @@ import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; * @class UmbStylesheetServerDataSource * @implements {UmbStylesheetServerDataSource} */ -export class UmbStylesheetServerDataSource implements UmbDataSource { +export class UmbStylesheetServerDataSource implements UmbDataSource { #host: UmbControllerHostElement; /** diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/sources/template.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/sources/template.detail.server.data.ts index 5fdffe4c7e..ccd865dcbb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/sources/template.detail.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/templating/templates/repository/sources/template.detail.server.data.ts @@ -16,7 +16,7 @@ import type { UmbDataSource } from '@umbraco-cms/backoffice/repository'; * @implements {TemplateDetailDataSource} */ export class UmbTemplateDetailServerDataSource - implements UmbDataSource + implements UmbDataSource { #host: UmbControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts index 42cb2af6b1..5130dc4484 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/dictionary.repository.ts @@ -18,6 +18,7 @@ export class UmbDictionaryRepository UmbTreeRepository, UmbDetailRepository< CreateDictionaryItemRequestModel, + any, UpdateDictionaryItemRequestModel, DictionaryOverviewResponseModel > diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts index ade44dc88c..412418ba01 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/translation/dictionary/repository/sources/dictionary.detail.server.data.ts @@ -19,7 +19,7 @@ import type { UmbDataSource } from '@umbraco-cms/backoffice/repository'; */ export class UmbDictionaryDetailServerDataSource implements - UmbDataSource + UmbDataSource { #host: UmbControllerHostElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user.store.ts index cf4823147e..e693f62bc0 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/current-user.store.ts @@ -1,5 +1,3 @@ -import { umbUsersData } from '../../../core/mocks/data/users.data'; -import { umbracoPath } from '@umbraco-cms/backoffice/utils'; import type { UserDetails } from '@umbraco-cms/backoffice/models'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; @@ -7,27 +5,6 @@ import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; export const UMB_CURRENT_USER_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbCurrentUserStore'); export class UmbCurrentUserStore { - //TODO: Temp solution to get a current user. Replace when we have a real user service - private _currentUser = new UmbObjectState(umbUsersData.getAll()[0]); - public readonly currentUser = this._currentUser.asObservable(); - - /** - * logs out the user - * @public - * @memberof UmbCurrentUserService - */ - public logout(): void { - fetch(umbracoPath('/user/logout').toString()) - .then((res) => res.json()) - .then((data) => { - console.log('User Logged out', data); - }); - } - - public get isAdmin(): boolean { - //TODO: Find a way to figure out if current user is in the admin group - const adminUserGroupKey = 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1'; - const currentUser = this._currentUser.getValue(); - return currentUser ? currentUser.userGroups.includes(adminUserGroupKey) : false; - } + #currentUser = new UmbObjectState(undefined); + public readonly currentUser = this.#currentUser.asObservable(); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/modals/current-user/current-user-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/modals/current-user/current-user-modal.element.ts index 78bb4d041a..578b9eb269 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/modals/current-user/current-user-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/modals/current-user/current-user-modal.element.ts @@ -8,8 +8,6 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @customElement('umb-current-user-modal') export class UmbCurrentUserModalElement extends UmbLitElement { - - @property({ attribute: false }) modalHandler?: UmbModalHandler; @@ -42,7 +40,7 @@ export class UmbCurrentUserModalElement extends UmbLitElement { } private _logout() { - this._currentUserStore?.logout(); + alert('implement log out'); } render() { @@ -58,7 +56,7 @@ export class UmbCurrentUserModalElement extends UmbLitElement { `; } - + static styles: CSSResultGroup = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-profile-apps/user-profile-app-profile.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-profile-apps/user-profile-app-profile.element.ts index 3c54bf51c1..c13879a26b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-profile-apps/user-profile-app-profile.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/current-user/user-profile-apps/user-profile-app-profile.element.ts @@ -8,8 +8,6 @@ import { UmbModalContext, UMB_CHANGE_PASSWORD_MODAL, UMB_MODAL_CONTEXT_TOKEN } f @customElement('umb-user-profile-app-profile') export class UmbUserProfileAppProfileElement extends UmbLitElement { - - @state() private _currentUser?: UserDetails; @@ -48,8 +46,9 @@ export class UmbUserProfileAppProfileElement extends UmbLitElement { private _changePassword() { if (!this._modalContext) return; + // TODO: check if current user is admin this._modalContext.open(UMB_CHANGE_PASSWORD_MODAL, { - requireOldPassword: this._currentUserStore?.isAdmin || false, + requireOldPassword: false, }); } @@ -62,7 +61,7 @@ export class UmbUserProfileAppProfileElement extends UmbLitElement { `; } - + static styles = [UUITextStyles, css``]; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/index.ts index 4886cd3909..4f92a3430e 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/index.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/index.ts @@ -1,3 +1,5 @@ +import { UmbEntrypointOnInit } from '@umbraco-cms/backoffice/extensions-api'; +import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; import { manifests as userGroupManifests } from './user-groups/manifests'; import { manifests as userManifests } from './users/manifests'; import { manifests as userSectionManifests } from './user-section/manifests'; @@ -8,8 +10,8 @@ import { UmbCurrentUserHistoryStore, UMB_CURRENT_USER_HISTORY_STORE_CONTEXT_TOKEN, } from './current-user/current-user-history.store'; -import { UmbEntrypointOnInit } from '@umbraco-cms/backoffice/extensions-api'; -import { UmbContextProviderController } from '@umbraco-cms/backoffice/context-api'; + +import './users/components'; export const manifests = [...userGroupManifests, ...userManifests, ...userSectionManifests, ...currentUserManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/manifests.ts index dd709645d0..9d27c3c675 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/manifests.ts @@ -1,5 +1,6 @@ import { manifests as repositoryManifests } from './repository/manifests'; import { manifests as workspaceManifests } from './workspace/manifests'; import { manifests as modalManifests } from './modals/manifests'; +import { manifests as sectionViewManifests } from './section-view/manifests'; -export const manifests = [...repositoryManifests, ...workspaceManifests, ...modalManifests]; +export const manifests = [...repositoryManifests, ...workspaceManifests, ...modalManifests, ...sectionViewManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/section-view/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/section-view/manifests.ts new file mode 100644 index 0000000000..0da2fefcd3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/section-view/manifests.ts @@ -0,0 +1,22 @@ +import { UMB_USER_SECTION_ALIAS } from '../../user-section/manifests'; +import type { ManifestSectionView } from '@umbraco-cms/backoffice/extensions-registry'; + +const sectionsViews: Array = [ + { + type: 'sectionView', + alias: 'Umb.SectionView.UserGroups', + name: 'User Groups Section View', + loader: () => import('./section-view-user-groups.element'), + weight: 100, + meta: { + label: 'User Groups', + pathname: 'user-groups', + icon: 'umb:users', + }, + conditions: { + sections: [UMB_USER_SECTION_ALIAS], + }, + }, +]; + +export const manifests = [...sectionsViews]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/section-view-user-groups.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/section-view/section-view-user-groups.element.ts similarity index 88% rename from src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/section-view-user-groups.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/section-view/section-view-user-groups.element.ts index c5abab729c..e88fd795a5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/section-view-user-groups.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/section-view/section-view-user-groups.element.ts @@ -2,8 +2,9 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html, LitElement } from 'lit'; import { customElement } from 'lit/decorators.js'; -import './workspace-view-user-groups.element'; +import '../workspace/workspace-view-user-groups.element'; +//TODO: rename to user-groups-section-view @customElement('umb-section-view-user-groups') export class UmbSectionViewUserGroupsElement extends LitElement { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-name-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-table-name-column-layout.element.ts similarity index 91% rename from src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-name-column-layout.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-table-name-column-layout.element.ts index b60dc36c88..d4cd1361c5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-name-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-table-name-column-layout.element.ts @@ -1,6 +1,6 @@ import { html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { UmbTableItem } from '../../../../shared/components/table'; +import { UmbTableItem } from '../../../shared/components/table'; @customElement('umb-user-group-table-name-column-layout') export class UmbUserGroupTableNameColumnLayoutElement extends LitElement { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-sections-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-table-sections-column-layout.element.ts similarity index 94% rename from src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-sections-column-layout.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-table-sections-column-layout.element.ts index 9171aa8c25..fc84192558 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/user-group-table-sections-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-table-sections-column-layout.element.ts @@ -1,6 +1,6 @@ import { html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { UmbTableItem } from '../../../../shared/components/table'; +import { UmbTableItem } from '../../../shared/components/table'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace-edit.element.ts index d803d7f7d8..16b1b6c3a2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace-edit.element.ts @@ -8,14 +8,12 @@ import { UmbUserGroupWorkspaceContext } from './user-group-workspace.context'; import type { UserGroupDetails } from '@umbraco-cms/backoffice/models'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import '../../../shared/components/input-user/input-user.element'; +import '../../users/components/user-input/user-input.element'; import '../../../shared/components/input-section/input-section.element'; import { UMB_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/context-api'; @customElement('umb-user-group-workspace-edit') export class UmbUserGroupWorkspaceEditElement extends UmbLitElement { - - defaultPermissions: Array<{ name: string; permissions: Array<{ name: string; description: string; value: boolean }>; @@ -151,23 +149,23 @@ export class UmbUserGroupWorkspaceEditElement extends UmbLitElement { this.observe(this.#workspaceContext.data, (userGroup) => (this._userGroup = userGroup as any)); } - private _observeUsers() { - if (!this._userStore) return; + // private _observeUsers() { + // if (!this._userStore) return; - // TODO: Create method to only get users from this userGroup - // TODO: Find a better way to only call this once at the start - this.observe(this._userStore.getAll(), (users) => { - // TODO: handle if there is no users. - if (!this._userKeys && users.length > 0) { - const entityId = this.#workspaceContext?.getEntityId(); - if (!entityId) return; - this._userKeys = users.filter((user) => user.userGroups.includes(entityId)).map((user) => user.id); - //this._updateProperty('users', this._userKeys); - // TODO: make a method on the UmbWorkspaceUserGroupContext: - //this._workspaceContext.setUsers(); - } - }); - } + // // TODO: Create method to only get users from this userGroup + // // TODO: Find a better way to only call this once at the start + // this.observe(this._userStore.getAll(), (users) => { + // // TODO: handle if there is no users. + // if (!this._userKeys && users.length > 0) { + // const entityId = this.#workspaceContext?.getEntityId(); + // if (!entityId) return; + // this._userKeys = users.filter((user) => user.userGroups.includes(entityId)).map((user) => user.id); + // //this._updateProperty('users', this._userKeys); + // // TODO: make a method on the UmbWorkspaceUserGroupContext: + // //this._workspaceContext.setUsers(); + // } + // }); + // } private _updateUserKeys(userKeys: Array) { this._userKeys = userKeys; @@ -306,7 +304,7 @@ export class UmbUserGroupWorkspaceEditElement extends UmbLitElement { `; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.test.ts deleted file mode 100644 index d7d85083ba..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/user-group-workspace.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -//TODO: Test has been commented out while we figure out how to setup import maps for the test environment -// import UmbWorkspaceUserGroupElement from './editor-user-group.element'; -// import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// describe('UmbWorkspaceUserGroupElement', () => { -// let element: UmbWorkspaceUserGroupElement; - -// beforeEach(async () => { -// element = await fixture(html` `); -// }); - -// it('is defined with its own instance', () => { -// expect(element).to.be.instanceOf(UmbWorkspaceUserGroupElement); -// }); - -// it('passes the a11y audit', async () => { -// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); -// }); -// }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/workspace-view-user-groups.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/workspace-view-user-groups.element.ts similarity index 93% rename from src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/workspace-view-user-groups.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/workspace-view-user-groups.element.ts index 84567fcf85..352ab18080 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/user-groups/workspace-view-user-groups.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-groups/workspace/workspace-view-user-groups.element.ts @@ -10,22 +10,17 @@ import { UmbTableItem, UmbTableOrderedEvent, UmbTableSelectedEvent, -} from '../../../../shared/components/table'; -import { - UmbUserGroupStore, - UMB_USER_GROUP_STORE_CONTEXT_TOKEN, -} from '../../../user-groups/repository/user-group.store'; +} from '../../../shared/components/table'; +import { UmbUserGroupStore, UMB_USER_GROUP_STORE_CONTEXT_TOKEN } from '../repository/user-group.store'; import type { UserGroupDetails } from '@umbraco-cms/backoffice/models'; import './user-group-table-name-column-layout.element'; -import './user-group-table-sections-column-layout.element'; +//import '../../user-section/views/user-groups/user-group-table-sections-column-layout.element'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; @customElement('umb-workspace-view-user-groups') export class UmbWorkspaceViewUserGroupsElement extends UmbLitElement { - - @state() private _userGroups: Array = []; @@ -140,7 +135,7 @@ export class UmbWorkspaceViewUserGroupsElement extends UmbLitElement { @ordered="${this._handleOrdering}"> `; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/manifests.ts index 69636ed32b..d6a5b8e8fe 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/manifests.ts @@ -1,10 +1,10 @@ -import type { ManifestSection, ManifestSectionView } from '@umbraco-cms/backoffice/extensions-registry'; +import type { ManifestSection } from '@umbraco-cms/backoffice/extensions-registry'; -const sectionAlias = 'Umb.Section.Users'; +export const UMB_USER_SECTION_ALIAS = 'Umb.Section.Users'; const section: ManifestSection = { type: 'section', - alias: sectionAlias, + alias: UMB_USER_SECTION_ALIAS, name: 'Users Section', weight: 100, meta: { @@ -13,37 +13,4 @@ const section: ManifestSection = { }, }; -const sectionsViews: Array = [ - { - type: 'sectionView', - alias: 'Umb.SectionView.Users.Users', - name: 'Users Section View', - loader: () => import('./views/users/section-view-users.element'), - weight: 200, - meta: { - label: 'Users', - pathname: 'users', - icon: 'umb:user', - }, - conditions: { - sections: [sectionAlias], - }, - }, - { - type: 'sectionView', - alias: 'Umb.SectionView.Users.UserGroups', - name: 'User Groups Section View', - loader: () => import('./views/user-groups/section-view-user-groups.element'), - weight: 100, - meta: { - label: 'User Groups', - pathname: 'user-groups', - icon: 'umb:users', - }, - conditions: { - sections: [sectionAlias], - }, - }, -]; - -export const manifests = [section, ...sectionsViews]; +export const manifests = [section]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/section-users.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/section-users.element.ts deleted file mode 100644 index 8a1899563e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/section-users.element.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { html, LitElement } from 'lit'; -import { customElement } from 'lit/decorators.js'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; -import type { ManifestSectionView } from '@umbraco-cms/backoffice/extensions-registry'; - -@customElement('umb-users-section') -export class UmbUsersSectionElement extends LitElement { - constructor() { - super(); - - this._registerSectionViews(); - } - - private _registerSectionViews() { - const manifests: Array = []; - - manifests.forEach((manifest) => { - if (umbExtensionsRegistry.isRegistered(manifest.alias)) return; - umbExtensionsRegistry.register(manifest); - }); - } - - render() { - return html` `; - } -} - -export default UmbUsersSectionElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-users-section': UmbUsersSectionElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.element.ts deleted file mode 100644 index 22b88cef10..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.element.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { css, html, nothing } from 'lit'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, state } from 'lit/decorators.js'; -import { repeat } from 'lit/directives/repeat.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import type { UmbSectionViewUsersElement } from '../../section-view-users.element'; -import { - UmbUserGroupStore, - UMB_USER_GROUP_STORE_CONTEXT_TOKEN, -} from '../../../../../user-groups/repository/user-group.store'; -import { getLookAndColorFromUserStatus } from '@umbraco-cms/backoffice/utils'; -import type { UserDetails, UserEntity, UserGroupEntity } from '@umbraco-cms/backoffice/models'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; - -@customElement('umb-workspace-view-users-grid') -export class UmbWorkspaceViewUsersGridElement extends UmbLitElement { - - - @state() - private _users: Array = []; - - @state() - private _selection: Array = []; - - @state() - private _userGroups: Array = []; - - private _userGroupStore?: UmbUserGroupStore; - private _usersContext?: UmbSectionViewUsersElement; - - constructor() { - super(); - - this.consumeContext(UMB_USER_GROUP_STORE_CONTEXT_TOKEN, (instance) => { - this._userGroupStore = instance; - this._observeUserGroups(); - }); - - this.consumeContext('umbUsersContext', (_instance) => { - this._usersContext = _instance; - this._observeUsers(); - this._observeSelection(); - }); - } - - private _observeUsers() { - if (!this._usersContext) return; - this.observe(this._usersContext.users, (users) => { - this._users = users; - }); - } - - private _observeUserGroups() { - if (!this._userGroupStore) return; - this.observe(this._userGroupStore.getAll(), (userGroups) => (this._userGroups = userGroups)); - } - - private _observeSelection() { - if (!this._usersContext) return; - this.observe(this._usersContext.selection, (selection) => (this._selection = selection)); - } - - private _isSelected(id: string) { - return this._selection.includes(id); - } - - //TODO How should we handle url stuff? - private _handleOpenCard(id: string) { - history.pushState(null, '', 'section/users/view/users/user/' + id); //TODO Change to a tag with href and make dynamic - } - - private _selectRowHandler(user: UserEntity) { - this._usersContext?.select(user.id); - } - - private _deselectRowHandler(user: UserEntity) { - this._usersContext?.deselect(user.id); - } - - private _getUserGroupNames(ids: Array) { - return ids - .map((id: string) => { - return this._userGroups.find((x) => x.id === id)?.name; - }) - .join(', '); - } - - private renderUserCard(user: UserDetails) { - if (!this._usersContext) return; - - const statusLook = getLookAndColorFromUserStatus(user.status); - - return html` - 0} - ?selected=${this._isSelected(user.id)} - @open=${() => this._handleOpenCard(user.id)} - @selected=${() => this._selectRowHandler(user)} - @unselected=${() => this._deselectRowHandler(user)}> - ${user.status && user.status !== 'enabled' - ? html` - ${user.status} - ` - : nothing} -
${this._getUserGroupNames(user.userGroups)}
- ${user.lastLoginDate - ? html`` - : html``} -
- `; - } - - render() { - return html` -
- ${repeat( - this._users, - (user) => user.id, - (user) => this.renderUserCard(user) - )} -
- `; - } - - static styles = [ - UUITextStyles, - css` - :host { - display: flex; - flex-direction: column; - } - - #user-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: var(--uui-size-space-4); - margin: var(--uui-size-layout-1); - margin-top: var(--uui-size-space-2); - } - - uui-card-user { - width: 100%; - height: 180px; - } - - .user-login-time { - margin-top: auto; - } - `, - ]; -} - -export default UmbWorkspaceViewUsersGridElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-workspace-view-users-grid': UmbWorkspaceViewUsersGridElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.test.ts deleted file mode 100644 index 42befffb67..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/grid/workspace-view-users-grid.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -import { UmbWorkspaceViewUsersGridElement } from './workspace-view-users-grid.element'; -import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -describe('UmbWorkspaceViewUsersCreateElement', () => { - let element: UmbWorkspaceViewUsersGridElement; - beforeEach(async () => { - element = await fixture(html``); - }); - - it('is defined with its own instance', () => { - expect(element).to.be.instanceOf(UmbWorkspaceViewUsersGridElement); - }); - - it('passes the a11y audit', async () => { - await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); - }); -}); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.element.ts deleted file mode 100644 index c9ae7da4a0..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.element.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { css, html } from 'lit'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, state } from 'lit/decorators.js'; -import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../../users/repository/user.store'; -import type { IRoute } from '@umbraco-cms/backoffice/router'; -import { umbExtensionsRegistry, createExtensionElement } from '@umbraco-cms/backoffice/extensions-api'; - -import './list-view-layouts/table/workspace-view-users-table.element'; -import './list-view-layouts/grid/workspace-view-users-grid.element'; -import './workspace-view-users-selection.element'; - -import type { UserDetails } from '@umbraco-cms/backoffice/models'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import { UmbDeepState } from '@umbraco-cms/backoffice/observable-api'; -import type { ManifestWorkspace } from '@umbraco-cms/backoffice/extensions-registry'; - -@customElement('umb-section-view-users') -export class UmbSectionViewUsersElement extends UmbLitElement { - - - @state() - private _routes: IRoute[] = []; - - private _workspaces: Array = []; - - // TODO: This must be turned into context api: Maybe its a Collection View (SectionView Collection View)? - private _userStore?: UmbUserStore; - - #selection = new UmbDeepState(>[]); - public readonly selection = this.#selection.asObservable(); - - #users = new UmbDeepState(>[]); - public readonly users = this.#users.asObservable(); - - #search = new UmbDeepState(''); - public readonly search = this.#search.asObservable(); - - constructor() { - super(); - - this.consumeContext(UMB_USER_STORE_CONTEXT_TOKEN, (_instance) => { - this._userStore = _instance; - this._observeUsers(); - }); - - // TODO: consider this context name, is it to broad? - // TODO: Stop using it self as a context api. - this.provideContext('umbUsersContext', this); - - this.observe(umbExtensionsRegistry?.extensionsOfType('workspace'), (workspaceExtensions) => { - this._workspaces = workspaceExtensions; - this._createRoutes(); - }); - } - - private _createRoutes() { - const routes: IRoute[] = [ - { - path: 'overview', - component: () => import('./workspace-view-users-overview.element'), - }, - ]; - - // TODO: find a way to make this reuseable across: - this._workspaces?.map((workspace: ManifestWorkspace) => { - routes.push({ - path: `${workspace.meta.entityType}/:id`, - component: () => createExtensionElement(workspace), - setup: (component, info) => { - if (component) { - (component as any).entityId = info.match.params.id; - } - }, - }); - routes.push({ - path: workspace.meta.entityType, - component: () => createExtensionElement(workspace), - }); - }); - - routes.push({ - path: '**', - redirectTo: 'overview', - }); - this._routes = routes; - } - - private _observeUsers() { - if (!this._userStore) return; - - if (this.#search.getValue()) { - this.observe(this._userStore.getByName(this.#search.getValue()), (users) => this.#users.next(users)); - } else { - this.observe(this._userStore.getAll(), (users) => this.#users.next(users)); - } - } - - public setSearch(value?: string) { - this.#search.next(value || ''); - this._observeUsers(); - this.requestUpdate('search'); - } - - public setSelection(value: Array) { - if (!value) return; - this.#selection.next(value); - this.requestUpdate('selection'); - } - - public select(id: string) { - const oldSelection = this.#selection.getValue(); - if (oldSelection.indexOf(id) !== -1) return; - - this.#selection.next([...oldSelection, id]); - this.requestUpdate('selection'); - } - - public deselect(id: string) { - const selection = this.#selection.getValue(); - this.#selection.next(selection.filter((k) => k !== id)); - this.requestUpdate('selection'); - } - - render() { - return html``; - } - - static styles = [ - UUITextStyles, - css` - :host { - height: 100%; - } - - #router-slot { - height: calc(100% - var(--umb-header-layout-height)); - } - `, - ]; -} - -export default UmbSectionViewUsersElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-section-view-users': UmbSectionViewUsersElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.test.ts deleted file mode 100644 index d43ea1fb0e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/section-view-users.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -//TODO: Test has been commented out while we figure out how to setup import maps for the test environment -// import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; -// import UmbSectionViewUsersElement from './section-view-users.element'; - -// describe('UmbSectionViewUsersElement', () => { -// let element: UmbSectionViewUsersElement; -// beforeEach(async () => { -// element = await fixture(html``); -// }); - -// it('is defined with its own instance', () => { -// expect(element).to.be.instanceOf(UmbSectionViewUsersElement); -// }); - -// it('passes the a11y audit', async () => { -// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); -// }); -// }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.element.ts deleted file mode 100644 index 73f6e14627..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.element.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { css, html, nothing } from 'lit'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, state } from 'lit/decorators.js'; -import { UUIPopoverElement } from '@umbraco-ui/uui'; -import type { UmbSectionViewUsersElement } from './section-view-users.element'; -import { - UmbModalContext, - UMB_MODAL_CONTEXT_TOKEN, - UMB_INVITE_USER_MODAL, - UMB_CREATE_USER_MODAL, -} from '@umbraco-cms/backoffice/modal'; -import type { IRoute } from '@umbraco-cms/backoffice/router'; - -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; - -import './list-view-layouts/table/workspace-view-users-table.element'; -import './list-view-layouts/grid/workspace-view-users-grid.element'; -import './workspace-view-users-selection.element'; - -export type UsersViewType = 'list' | 'grid'; -@customElement('umb-workspace-view-users-overview') -export class UmbWorkspaceViewUsersOverviewElement extends UmbLitElement { - - - @state() - private _selection: Array = []; - - @state() - private isCloud = false; //NOTE: Used to show either invite or create user buttons and views. - - @state() - private _routes: IRoute[] = [ - { - path: 'grid', - component: () => import('./list-view-layouts/grid/workspace-view-users-grid.element'), - }, - { - path: 'list', - component: () => import('./list-view-layouts/table/workspace-view-users-table.element'), - }, - { - path: '**', - redirectTo: 'grid', - }, - ]; - - private _usersContext?: UmbSectionViewUsersElement; - private _modalContext?: UmbModalContext; - private _inputTimer?: NodeJS.Timeout; - private _inputTimerAmount = 500; - - connectedCallback(): void { - super.connectedCallback(); - - this.consumeContext('umbUsersContext', (usersContext: UmbSectionViewUsersElement) => { - this._usersContext = usersContext; - this._observeSelection(); - }); - - this.consumeContext(UMB_MODAL_CONTEXT_TOKEN, (instance) => { - this._modalContext = instance; - }); - } - - private _observeSelection() { - if (!this._usersContext) return; - this.observe(this._usersContext.selection, (selection) => (this._selection = selection)); - } - - private _toggleViewType() { - const isList = window.location.pathname.split('/').pop() === 'list'; - - isList - ? history.pushState(null, '', 'section/users/view/users/overview/grid') - : history.pushState(null, '', 'section/users/view/users/overview/list'); - } - - private _renderSelection() { - if (this._selection.length === 0) return nothing; - - return html``; - } - - private _handleTogglePopover(event: PointerEvent) { - const composedPath = event.composedPath(); - - const popover = composedPath.find((el) => el instanceof UUIPopoverElement) as UUIPopoverElement; - if (popover) { - popover.open = !popover.open; - } - } - - private _updateSearch(event: InputEvent) { - const target = event.target as HTMLInputElement; - const search = target.value || ''; - clearTimeout(this._inputTimer); - this._inputTimer = setTimeout(() => this._refreshUsers(search), this._inputTimerAmount); - } - - private _refreshUsers(search: string) { - if (!this._usersContext) return; - this._usersContext.setSearch(search); - } - - private _showInviteOrCreate() { - let token = undefined; - // TODO: we need to find a better way to determine if we should create or invite - if (this.isCloud) { - token = UMB_INVITE_USER_MODAL; - } else { - token = UMB_CREATE_USER_MODAL; - } - - this._modalContext?.open(token); - } - - render() { - return html` - -
-
- - -
- - - - Status: All - -
- - - - -
-
- - - Groups: All - -
- - - - -
-
- - - Order by: Name (A-Z) - -
- - - - -
-
- - - -
-
-
- - -
- - ${this._renderSelection()} - `; - } - - static styles = [ - UUITextStyles, - css` - :host { - height: 100%; - display: flex; - flex-direction: column; - } - - #sticky-top { - position: sticky; - top: 0px; - z-index: 1; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0), 0 1px 2px rgba(0, 0, 0, 0); - transition: 250ms box-shadow ease-in-out; - } - - #sticky-top.header-shadow { - box-shadow: var(--uui-shadow-depth-2); - } - - #user-list-top-bar { - padding: var(--uui-size-space-4) var(--uui-size-layout-1); - background-color: var(--uui-color-background); - display: flex; - justify-content: space-between; - white-space: nowrap; - gap: var(--uui-size-space-5); - align-items: center; - } - #user-list { - padding: var(--uui-size-layout-1); - padding-top: var(--uui-size-space-2); - } - #input-search { - width: 100%; - } - - uui-popover { - width: unset; - } - - .filter-dropdown { - display: flex; - gap: var(--uui-size-space-3); - flex-direction: column; - background-color: var(--uui-color-surface); - padding: var(--uui-size-space-4); - border-radius: var(--uui-size-border-radius); - box-shadow: var(--uui-shadow-depth-2); - width: fit-content; - } - a { - color: inherit; - text-decoration: none; - } - `, - ]; -} - -export default UmbWorkspaceViewUsersOverviewElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-workspace-view-users-overview': UmbWorkspaceViewUsersOverviewElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.test.ts deleted file mode 100644 index 4058b4be91..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-overview.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -//TODO: Test has been commented out while we figure out how to setup import maps for the test environment -// import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; -// import UmbWorkspaceViewUsersOverviewElement from './workspace-view-users-overview.element'; - -// describe('UmbWorkspaceViewUsersOverviewElement', () => { -// let element: UmbWorkspaceViewUsersOverviewElement; -// beforeEach(async () => { -// element = await fixture(html``); -// }); - -// it('is defined with its own instance', () => { -// expect(element).to.be.instanceOf(UmbWorkspaceViewUsersOverviewElement); -// }); - -// it('passes the a11y audit', async () => { -// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); -// }); -// }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.element.ts deleted file mode 100644 index 8ce1af9b6e..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.element.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { css, html } from 'lit'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, state } from 'lit/decorators.js'; -import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../../../users/users/repository/user.store'; -import { UmbSectionViewUsersElement } from './section-view-users.element'; -import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; - -@customElement('umb-workspace-view-users-selection') -export class UmbWorkspaceViewUsersSelectionElement extends UmbLitElement { - - - @state() - private _selection: Array = []; - - @state() - private _totalUsers = 0; - - private _usersContext?: UmbSectionViewUsersElement; - private _userStore?: UmbUserStore; - - connectedCallback(): void { - super.connectedCallback(); - this.consumeContext('umbUsersContext', (usersContext: UmbSectionViewUsersElement) => { - this._usersContext = usersContext; - this._observeSelection(); - }); - - this.consumeContext(UMB_USER_STORE_CONTEXT_TOKEN, (userStore) => { - this._userStore = userStore; - this._observeTotalUsers(); - }); - } - - private _observeSelection() { - if (!this._usersContext) return; - this.observe(this._usersContext.selection, (selection) => (this._selection = selection)); - } - - private _observeTotalUsers() { - if (!this._userStore) return; - this.observe(this._userStore.totalUsers, (totalUsers) => (this._totalUsers = totalUsers)); - } - - private _handleClearSelection() { - this._usersContext?.setSelection([]); - } - - private _renderSelectionCount() { - return html`
${this._selection.length} of ${this._totalUsers} selected
`; - } - - render() { - return html` - ${this._renderSelectionCount()} - - - - `; - } - - static styles = [ - UUITextStyles, - css` - :host { - display: flex; - gap: var(--uui-size-3); - width: 100%; - padding: var(--uui-size-space-4) var(--uui-size-space-6); - background-color: var(--uui-color-selected); - color: var(--uui-color-selected-contrast); - align-items: center; - box-sizing: border-box; - } - `, - ]; -} - -export default UmbWorkspaceViewUsersSelectionElement; - -declare global { - interface HTMLElementTagNameMap { - 'umb-workspace-view-users-selection': UmbWorkspaceViewUsersSelectionElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.test.ts deleted file mode 100644 index d889b7f951..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/workspace-view-users-selection.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -//TODO: Test has been commented out while we figure out how to setup import maps for the test environment -// import UmbWorkspaceViewUsersSelectionElement from './workspace-view-users-selection.element'; -// import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// describe('UmbWorkspaceViewUsersSelectionElement', () => { -// let element: UmbWorkspaceViewUsersSelectionElement; -// beforeEach(async () => { -// element = await fixture(html``); -// }); - -// it('is defined with its own instance', () => { -// expect(element).to.be.instanceOf(UmbWorkspaceViewUsersSelectionElement); -// }); - -// it('passes the a11y audit', async () => { -// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); -// }); -// }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection-header.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection-header.element.ts new file mode 100644 index 0000000000..f6d9438206 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection-header.element.ts @@ -0,0 +1,185 @@ +import { UUITextStyles } from '@umbraco-ui/uui-css'; +import { css, html } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { UUIPopoverElement } from '@umbraco-ui/uui'; +import { UMB_COLLECTION_CONTEXT_TOKEN } from '../../../shared/components/collection/collection.context'; +import { UmbUserCollectionContext } from './user-collection.context'; +import { + UMB_CREATE_USER_MODAL, + UMB_INVITE_USER_MODAL, + UMB_MODAL_CONTEXT_TOKEN, + UmbModalContext, +} from '@umbraco-cms/backoffice/modal'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; + +@customElement('umb-user-collection-header') +export class UmbUserCollectionHeaderElement extends UmbLitElement { + @state() + private isCloud = true; //NOTE: Used to show either invite or create user buttons and views. + + #modalContext?: UmbModalContext; + #collectionContext?: UmbUserCollectionContext; + #inputTimer?: NodeJS.Timeout; + #inputTimerAmount = 500; + + constructor() { + super(); + + this.consumeContext(UMB_MODAL_CONTEXT_TOKEN, (instance) => { + this.#modalContext = instance; + }); + + this.consumeContext(UMB_COLLECTION_CONTEXT_TOKEN, (instance) => { + this.#collectionContext = instance; + }); + } + + private _toggleViewType() { + const isList = window.location.pathname.split('/').pop() === 'list'; + + isList + ? history.pushState(null, '', 'section/users/view/users/overview/grid') + : history.pushState(null, '', 'section/users/view/users/overview/list'); + } + + private _handleTogglePopover(event: PointerEvent) { + const composedPath = event.composedPath(); + + const popover = composedPath.find((el) => el instanceof UUIPopoverElement) as UUIPopoverElement; + if (popover) { + popover.open = !popover.open; + } + } + + private _updateSearch(event: InputEvent) { + const target = event.target as HTMLInputElement; + const filter = target.value || ''; + clearTimeout(this.#inputTimer); + this.#inputTimer = setTimeout(() => this.#collectionContext?.setFilter({ filter }), this.#inputTimerAmount); + } + + private _showInviteOrCreate() { + let token = undefined; + // TODO: we need to find a better way to determine if we should create or invite + if (this.isCloud) { + token = UMB_INVITE_USER_MODAL; + } else { + token = UMB_CREATE_USER_MODAL; + } + + this.#modalContext?.open(token); + } + + render() { + return html` +
+
+ + +
+ + + + Status: All + +
+ + + + +
+
+ + + Groups: All + +
+ + + + +
+
+ + + Order by: Name (A-Z) + +
+ + + + +
+
+ + + +
+
+
+ `; + } + static styles = [ + UUITextStyles, + css` + #sticky-top { + position: sticky; + top: 0px; + z-index: 1; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0), 0 1px 2px rgba(0, 0, 0, 0); + transition: 250ms box-shadow ease-in-out; + } + + #sticky-top.header-shadow { + box-shadow: var(--uui-shadow-depth-2); + } + + #user-list-top-bar { + padding: var(--uui-size-space-4) var(--uui-size-layout-1); + background-color: var(--uui-color-background); + display: flex; + justify-content: space-between; + white-space: nowrap; + gap: var(--uui-size-space-5); + align-items: center; + } + #user-list { + padding: var(--uui-size-layout-1); + padding-top: var(--uui-size-space-2); + } + #input-search { + width: 100%; + } + + uui-popover { + width: unset; + } + + .filter-dropdown { + display: flex; + gap: var(--uui-size-space-3); + flex-direction: column; + background-color: var(--uui-color-surface); + padding: var(--uui-size-space-4); + border-radius: var(--uui-size-border-radius); + box-shadow: var(--uui-shadow-depth-2); + width: fit-content; + } + a { + color: inherit; + text-decoration: none; + } + `, + ]; +} + +export default UmbUserCollectionHeaderElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-user-collection-header': UmbUserCollectionHeaderElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection.context.ts new file mode 100644 index 0000000000..a849869a93 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection.context.ts @@ -0,0 +1,11 @@ +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { USER_REPOSITORY_ALIAS } from '../repository/manifests'; +import { UmbCollectionContext } from '../../../shared/components/collection/collection.context'; +import { UmbUserCollectionFilterModel } from '../types'; + +export class UmbUserCollectionContext extends UmbCollectionContext { + constructor(host: UmbControllerHostElement) { + super(host, 'user', USER_REPOSITORY_ALIAS); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection.element.ts new file mode 100644 index 0000000000..5ebc21aad2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/user-collection.element.ts @@ -0,0 +1,68 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, state } from 'lit/decorators.js'; +import type { IRoute } from '@umbraco-cms/backoffice/router'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UMB_COLLECTION_CONTEXT_TOKEN } from '../../../shared/components/collection/collection.context'; +import { UmbUserCollectionContext } from './user-collection.context'; + +import './views/table/user-table-collection-view.element'; +import './views/grid/user-grid-collection-view.element'; +import './user-collection-header.element'; + +export type UsersViewType = 'list' | 'grid'; +@customElement('umb-user-collection') +export class UmbUserCollectionElement extends UmbLitElement { + #collectionContext = new UmbUserCollectionContext(this); + + @state() + private _routes: IRoute[] = [ + { + path: 'grid', + component: () => import('./views/grid/user-grid-collection-view.element'), + }, + { + path: 'list', + component: () => import('./views/table/user-table-collection-view.element'), + }, + { + path: '**', + redirectTo: 'grid', + }, + ]; + + connectedCallback(): void { + super.connectedCallback(); + this.provideContext(UMB_COLLECTION_CONTEXT_TOKEN, this.#collectionContext); + } + + render() { + return html` + + + + + + + `; + } + + static styles = [ + UUITextStyles, + css` + :host { + height: 100%; + display: flex; + flex-direction: column; + } + `, + ]; +} + +export default UmbUserCollectionElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-user-collection': UmbUserCollectionElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/grid/user-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/grid/user-grid-collection-view.element.ts new file mode 100644 index 0000000000..fb49273650 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/grid/user-grid-collection-view.element.ts @@ -0,0 +1,135 @@ +import { css, html, nothing } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, state } from 'lit/decorators.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { UMB_COLLECTION_CONTEXT_TOKEN } from '../../../../../shared/components/collection/collection.context'; +import { getLookAndColorFromUserStatus } from '../../../../utils'; +import { UmbUserCollectionContext } from '../../user-collection.context'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UserResponseModel, UserStateModel } from '@umbraco-cms/backoffice/backend-api'; + +@customElement('umb-user-grid-collection-view') +export class UmbUserGridCollectionViewElement extends UmbLitElement { + @state() + private _users: Array = []; + + @state() + private _selection: Array = []; + + #collectionContext?: UmbUserCollectionContext; + + constructor() { + super(); + + //TODO: Get user group names + + this.consumeContext(UMB_COLLECTION_CONTEXT_TOKEN, (instance) => { + this.#collectionContext = instance; + this.observe(this.#collectionContext.selection, (selection) => (this._selection = selection)); + this.observe(this.#collectionContext.items, (items) => (this._users = items)); + }); + } + + //TODO How should we handle url stuff? + private _handleOpenCard(id: string) { + //TODO this will not be needed when cards works as links with href + history.pushState(null, '', 'section/users/view/users/user/' + id); //TODO Change to a tag with href and make dynamic + } + + #onSelect(user: UserResponseModel) { + this.#collectionContext?.select(user.id ?? ''); + } + + #onDeselect(user: UserResponseModel) { + this.#collectionContext?.deselect(user.id ?? ''); + } + + #renderUserCard(user: UserResponseModel) { + return html` + 0} + ?selected=${this.#collectionContext?.isSelected(user.id ?? '')} + @open=${() => this._handleOpenCard(user.id ?? '')} + @selected=${() => this.#onSelect(user)} + @unselected=${() => this.#onDeselect(user)}> + ${this.#renderUserTag(user)} ${this.#renderUserLoginDate(user)} + + `; + } + + #renderUserTag(user: UserResponseModel) { + if (user.state || user.state === UserStateModel.ACTIVE) { + return nothing; + } + + const statusLook = getLookAndColorFromUserStatus(user.state); + return html` + ${user.state} + `; + } + + #renderUserLoginDate(user: UserResponseModel) { + if (!user.lastLoginDate) { + return html``; + } + + return html``; + } + + render() { + return html` +
+ ${repeat( + this._users, + (user) => user.id, + (user) => this.#renderUserCard(user) + )} +
+ `; + } + + static styles = [ + UUITextStyles, + css` + :host { + display: flex; + flex-direction: column; + } + + #user-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: var(--uui-size-space-4); + margin: var(--uui-size-layout-1); + margin-top: var(--uui-size-space-2); + } + + uui-card-user { + width: 100%; + height: 180px; + } + + .user-login-time { + margin-top: auto; + } + `, + ]; +} + +export default UmbUserGridCollectionViewElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-user-grid-collection-view': UmbUserGridCollectionViewElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/column-layouts/name/user-table-name-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/table/column-layouts/name/user-table-name-column-layout.element.ts similarity index 95% rename from src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/column-layouts/name/user-table-name-column-layout.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/table/column-layouts/name/user-table-name-column-layout.element.ts index ed36318416..d88058e1d4 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/column-layouts/name/user-table-name-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/table/column-layouts/name/user-table-name-column-layout.element.ts @@ -1,6 +1,6 @@ import { html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import type { UmbTableColumn, UmbTableItem } from '../../../../../../../../shared/components/table/table.element'; +import type { UmbTableColumn, UmbTableItem } from '../../../../../../../shared/components/table/table.element'; @customElement('umb-user-table-name-column-layout') export class UmbUserTableNameColumnLayoutElement extends LitElement { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/column-layouts/status/user-table-status-column-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/table/column-layouts/status/user-table-status-column-layout.element.ts similarity index 90% rename from src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/column-layouts/status/user-table-status-column-layout.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/table/column-layouts/status/user-table-status-column-layout.element.ts index 75ca3b5fb2..5ac48eced6 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/column-layouts/status/user-table-status-column-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/table/column-layouts/status/user-table-status-column-layout.element.ts @@ -1,6 +1,6 @@ import { html, LitElement, nothing } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { getLookAndColorFromUserStatus } from '@umbraco-cms/backoffice/utils'; +import { getLookAndColorFromUserStatus } from '../../../../../../utils'; @customElement('umb-user-table-status-column-layout') export class UmbUserTableStatusColumnLayoutElement extends LitElement { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/workspace-view-users-table.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/table/user-table-collection-view.element.ts similarity index 70% rename from src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/workspace-view-users-table.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/table/user-table-collection-view.element.ts index ec4a433208..d3c8b7433f 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/user-section/views/users/list-view-layouts/table/workspace-view-users-table.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/collection/views/table/user-table-collection-view.element.ts @@ -1,7 +1,6 @@ import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; -import type { UmbSectionViewUsersElement } from '../../section-view-users.element'; import { UmbTableElement, UmbTableColumn, @@ -10,24 +9,25 @@ import { UmbTableSelectedEvent, UmbTableConfig, UmbTableOrderedEvent, -} from '../../../../../../shared/components/table/table.element'; +} from '../../../../../shared/components/table/table.element'; import { UmbUserGroupStore, UMB_USER_GROUP_STORE_CONTEXT_TOKEN, -} from '../../../../../user-groups/repository/user-group.store'; -import type { UserDetails, UserGroupEntity } from '@umbraco-cms/backoffice/models'; +} from '../../../../user-groups/repository/user-group.store'; +import { + UMB_COLLECTION_CONTEXT_TOKEN, + UmbCollectionContext, +} from '../../../../../shared/components/collection/collection.context'; +import type { UserGroupEntity } from '@umbraco-cms/backoffice/models'; import './column-layouts/name/user-table-name-column-layout.element'; import './column-layouts/status/user-table-status-column-layout.element'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbUserCollectionContext } from '../../user-collection.context'; -@customElement('umb-workspace-view-users-table') -export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { - - - @state() - private _users: Array = []; - +@customElement('umb-user-table-collection-view') +export class UmbUserTableCollectionViewElement extends UmbLitElement { @state() private _tableConfig: UmbTableConfig = { allowSelection: true, @@ -58,14 +58,18 @@ export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { @state() private _tableItems: Array = []; - @state() - private _selection: Array = []; - @state() private _userGroups: Array = []; private _userGroupStore?: UmbUserGroupStore; - private _usersContext?: UmbSectionViewUsersElement; + + @state() + private _users: Array = []; + + @state() + private _selection: Array = []; + + #collectionContext?: UmbUserCollectionContext; constructor() { super(); @@ -75,26 +79,10 @@ export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { this._observeUserGroups(); }); - this.consumeContext('umbUsersContext', (_instance) => { - this._usersContext = _instance; - this._observeUsers(); - this._observeSelection(); - }); - } - - private _observeUsers() { - if (!this._usersContext) return; - this.observe(this._usersContext.users, (users) => { - this._users = users; - this._createTableItems(this._users); - }); - } - - private _observeSelection() { - if (!this._usersContext) return; - this.observe(this._usersContext.selection, (selection) => { - if (this._selection === selection) return; - this._selection = selection; + this.consumeContext(UMB_COLLECTION_CONTEXT_TOKEN, (instance) => { + this.#collectionContext = instance; + this.observe(this.#collectionContext.selection, (selection) => (this._selection = selection)); + this.observe(this.#collectionContext.items, (items) => (this._users = items)); }); } @@ -114,10 +102,10 @@ export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { .join(', '); } - private _createTableItems(users: Array) { + private _createTableItems(users: Array) { this._tableItems = users.map((user) => { return { - id: user.id, + id: user.id ?? '', icon: 'umb:user', data: [ { @@ -128,7 +116,7 @@ export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { }, { columnAlias: 'userGroup', - value: this._getUserGroupNames(user.userGroups), + value: this._getUserGroupNames(user.userGroupIds ?? []), }, { columnAlias: 'userLastLogin', @@ -137,7 +125,7 @@ export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { { columnAlias: 'userStatus', value: { - status: user.status, + status: user.state, }, }, ], @@ -149,14 +137,14 @@ export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { event.stopPropagation(); const table = event.target as UmbTableElement; const selection = table.selection; - this._usersContext?.setSelection(selection); + this.#collectionContext?.setSelection(selection); } private _handleDeselected(event: UmbTableDeselectedEvent) { event.stopPropagation(); const table = event.target as UmbTableElement; const selection = table.selection; - this._usersContext?.setSelection(selection); + this.#collectionContext?.setSelection(selection); } private _handleOrdering(event: UmbTableOrderedEvent) { @@ -178,7 +166,7 @@ export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { @ordered="${this._handleOrdering}"> `; } - + static styles = [ UUITextStyles, css` @@ -195,10 +183,10 @@ export class UmbWorkspaceViewUsersTableElement extends UmbLitElement { ]; } -export default UmbWorkspaceViewUsersTableElement; +export default UmbUserTableCollectionViewElement; declare global { interface HTMLElementTagNameMap { - 'umb-workspace-view-users-table': UmbWorkspaceViewUsersTableElement; + 'umb-workspace-view-users-table': UmbUserTableCollectionViewElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/index.ts new file mode 100644 index 0000000000..df0765ef5b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/index.ts @@ -0,0 +1 @@ +import './user-input/user-input.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.context.ts new file mode 100644 index 0000000000..7a474bed3f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.context.ts @@ -0,0 +1,10 @@ +import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UMB_USER_PICKER_MODAL } from '@umbraco-cms/backoffice/modal'; +import { UserItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +export class UmbUserPickerContext extends UmbPickerInputContext { + constructor(host: UmbControllerHostElement) { + super(host, 'Umb.Repository.User', UMB_USER_PICKER_MODAL); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.element.ts new file mode 100644 index 0000000000..167ab7e678 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.element.ts @@ -0,0 +1,136 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, property, state } from 'lit/decorators.js'; +import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; +import { UmbUserPickerContext } from './user-input.context'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import type { UserItemResponseModel } from '@umbraco-cms/backoffice/backend-api'; + +@customElement('umb-user-input') +export class UmbUserInputElement extends FormControlMixin(UmbLitElement) { + /** + * This is a minimum amount of selected items in this input. + * @type {number} + * @attr + * @default 0 + */ + @property({ type: Number }) + public get min(): number { + return this.#pickerContext.min; + } + public set min(value: number) { + this.#pickerContext.min = value; + } + + /** + * Min validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + minMessage = 'This field need more items'; + + /** + * This is a maximum amount of selected items in this input. + * @type {number} + * @attr + * @default Infinity + */ + @property({ type: Number }) + public get max(): number { + return this.#pickerContext.max; + } + public set max(value: number) { + this.#pickerContext.max = value; + } + + /** + * Max validation message. + * @type {boolean} + * @attr + * @default + */ + @property({ type: String, attribute: 'min-message' }) + maxMessage = 'This field exceeds the allowed amount of items'; + + public get selectedIds(): Array { + return this.#pickerContext.getSelection(); + } + public set selectedIds(ids: Array) { + this.#pickerContext.setSelection(ids); + } + + @property() + public set value(idsString: string) { + // Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection. + this.selectedIds = idsString.split(/[ ,]+/); + } + + @state() + private _items?: Array; + + #pickerContext = new UmbUserPickerContext(this); + + constructor() { + super(); + + this.addValidator( + 'rangeUnderflow', + () => this.minMessage, + () => !!this.min && this.#pickerContext.getSelection().length < this.min + ); + + this.addValidator( + 'rangeOverflow', + () => this.maxMessage, + () => !!this.max && this.#pickerContext.getSelection().length > this.max + ); + + this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(','))); + this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems)); + } + + protected getFormElement() { + return undefined; + } + + render() { + return html` + ${this._items?.map((item) => this._renderItem(item))} + this.#pickerContext.openPicker()} label="open" + >Add + `; + } + + private _renderItem(item: UserItemResponseModel) { + if (!item.id) return; + return html` + + + this.#pickerContext.requestRemoveItem(item.id!)} label="Remove ${item.name}" + >Remove + + + `; + } + + static styles = [ + UUITextStyles, + css` + #add-button { + width: 100%; + } + `, + ]; +} + +export default UmbUserInputElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-user-input': UmbUserInputElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.stories.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.stories.ts new file mode 100644 index 0000000000..6ef641f6ef --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/components/user-input/user-input.stories.ts @@ -0,0 +1,31 @@ +import { Meta, StoryObj } from '@storybook/web-components'; +import './user-input.element'; +import type { UmbUserInputElement } from './user-input.element'; + +const meta: Meta = { + title: 'Components/Inputs/User', + component: 'umb-user-input', + argTypes: { + /* + modalType: { + control: 'inline-radio', + options: ['dialog', 'sidebar'], + defaultValue: 'sidebar', + description: 'The type of modal to use when selecting users', + }, + modalSize: { + control: 'select', + options: ['small', 'medium', 'large', 'full'], + defaultValue: 'small', + description: 'The size of the modal to use when selecting users, only applicable to sidebar not dialog', + }, + */ + }, +}; + +export default meta; +type Story = StoryObj; + +export const Overview: Story = { + args: {}, +}; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/delete/delete.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/delete/delete.action.ts new file mode 100644 index 0000000000..85c96fe127 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/delete/delete.action.ts @@ -0,0 +1,24 @@ +import { html } from 'lit'; + +import { UmbUserRepository } from '../../repository/user.repository'; +import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; +import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/modal'; + +export class UmbUserDeleteEntityBulkAction extends UmbEntityBulkActionBase { + #modalContext?: UmbModalContext; + + constructor(host: UmbControllerHostElement, repositoryAlias: string, selection: Array) { + super(host, repositoryAlias, selection); + + new UmbContextConsumerController(host, UMB_MODAL_CONTEXT_TOKEN, (instance) => { + this.#modalContext = instance; + }); + } + + async execute() { + //TODO: we need bulk actions on the server + alert('Bulk delete is not implemented yet'); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/disable/disable.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/disable/disable.action.ts new file mode 100644 index 0000000000..559f9cfeaa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/disable/disable.action.ts @@ -0,0 +1,14 @@ +import { UmbUserRepository } from '../../repository/user.repository'; +import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; + +export class UmbDisableUserEntityBulkAction extends UmbEntityBulkActionBase { + constructor(host: UmbControllerHostElement, repositoryAlias: string, selection: Array) { + super(host, repositoryAlias, selection); + } + + async execute() { + //TODO: Implement + alert('Bulk disable is not implemented yet'); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/enable/enable.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/enable/enable.action.ts new file mode 100644 index 0000000000..19ac7e5322 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/enable/enable.action.ts @@ -0,0 +1,14 @@ +import { UmbUserRepository } from '../../repository/user.repository'; +import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; + +export class UmbEnableUserEntityBulkAction extends UmbEntityBulkActionBase { + constructor(host: UmbControllerHostElement, repositoryAlias: string, selection: Array) { + super(host, repositoryAlias, selection); + } + + async execute() { + //TODO: Implement + alert('Bulk enable is not implemented yet'); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/manifests.ts new file mode 100644 index 0000000000..c55e3c7353 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/manifests.ts @@ -0,0 +1,70 @@ +import { USER_REPOSITORY_ALIAS } from '../repository/manifests'; +import { UmbUserDeleteEntityBulkAction } from './delete/delete.action'; +import { UmbEnableUserEntityBulkAction } from './enable/enable.action'; +import { UmbSetGroupUserEntityBulkAction } from './set-group/set-group.action'; +import { UmbUnlockUserEntityBulkAction } from './unlock/unlock.action'; +import { UmbDisableUserEntityBulkAction } from './disable/disable.action'; +import { ManifestEntityBulkAction } from '@umbraco-cms/backoffice/extensions-registry'; + +const entityType = 'user'; + +const entityActions: Array = [ + { + type: 'entityBulkAction', + alias: 'Umb.EntityBulkAction.User.SetGroup', + name: 'SetGroup User Entity Bulk Action', + weight: 400, + meta: { + label: 'SetGroup', + repositoryAlias: USER_REPOSITORY_ALIAS, + api: UmbSetGroupUserEntityBulkAction, + }, + conditions: { + entityType, + }, + }, + { + type: 'entityBulkAction', + alias: 'Umb.EntityBulkAction.User.Enable', + name: 'Enable User Entity Bulk Action', + weight: 300, + meta: { + label: 'Enable', + repositoryAlias: USER_REPOSITORY_ALIAS, + api: UmbEnableUserEntityBulkAction, + }, + conditions: { + entityType, + }, + }, + { + type: 'entityBulkAction', + alias: 'Umb.EntityBulkAction.User.Unlock', + name: 'Unlock User Entity Bulk Action', + weight: 200, + meta: { + label: 'Unlock', + repositoryAlias: USER_REPOSITORY_ALIAS, + api: UmbUnlockUserEntityBulkAction, + }, + conditions: { + entityType, + }, + }, + { + type: 'entityBulkAction', + alias: 'Umb.EntityBulkAction.User.Disable', + name: 'Disable User Entity Bulk Action', + weight: 100, + meta: { + label: 'Disable', + repositoryAlias: USER_REPOSITORY_ALIAS, + api: UmbDisableUserEntityBulkAction, + }, + conditions: { + entityType, + }, + }, +]; + +export const manifests = [...entityActions]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/set-group/set-group.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/set-group/set-group.action.ts new file mode 100644 index 0000000000..e94974d257 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/set-group/set-group.action.ts @@ -0,0 +1,24 @@ +import { UmbUserRepository } from '../../repository/user.repository'; +import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; +import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/modal'; + +export class UmbSetGroupUserEntityBulkAction extends UmbEntityBulkActionBase { + #modalContext?: UmbModalContext; + + constructor(host: UmbControllerHostElement, repositoryAlias: string, selection: Array) { + super(host, repositoryAlias, selection); + + new UmbContextConsumerController(host, UMB_MODAL_CONTEXT_TOKEN, (instance) => { + this.#modalContext = instance; + + //TODO: add user group picker modal + }); + } + + async execute() { + //TODO: Implement + alert('Bulk set group is not implemented yet'); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/unlock/unlock.action.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/unlock/unlock.action.ts new file mode 100644 index 0000000000..54ae6c49fc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/entity-bulk-actions/unlock/unlock.action.ts @@ -0,0 +1,14 @@ +import { UmbUserRepository } from '../../repository/user.repository'; +import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; + +export class UmbUnlockUserEntityBulkAction extends UmbEntityBulkActionBase { + constructor(host: UmbControllerHostElement, repositoryAlias: string, selection: Array) { + super(host, repositoryAlias, selection); + } + + async execute() { + //TODO: Implement + alert('Bulk unlock is not implemented yet'); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/manifests.ts index dd709645d0..59c0a2fe23 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/manifests.ts @@ -1,5 +1,13 @@ import { manifests as repositoryManifests } from './repository/manifests'; import { manifests as workspaceManifests } from './workspace/manifests'; import { manifests as modalManifests } from './modals/manifests'; +import { manifests as sectionViewManifests } from './section-view/manifests'; +import { manifests as entityBulkActionManifests } from './entity-bulk-actions/manifests'; -export const manifests = [...repositoryManifests, ...workspaceManifests, ...modalManifests]; +export const manifests = [ + ...repositoryManifests, + ...workspaceManifests, + ...modalManifests, + ...sectionViewManifests, + ...entityBulkActionManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/create-user/create-user-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/create-user/create-user-modal.element.ts index 095e3292fa..17f8ee41dc 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/create-user/create-user-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/create-user/create-user-modal.element.ts @@ -2,43 +2,43 @@ import { css, html, nothing } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, query, state } from 'lit/decorators.js'; import { UUIInputPasswordElement } from '@umbraco-ui/uui'; -import { UmbInputPickerUserGroupElement } from '../../../../shared/components/input-user-group/input-user-group.element'; import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../repository/user.store'; +import { UmbInputPickerUserGroupElement } from '../../../../shared/components/input-user-group/input-user-group.element'; +import { UmbUserRepository } from '../../repository/user.repository'; import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; -import type { UserDetails } from '@umbraco-cms/backoffice/models'; import { UmbNotificationDefaultData, UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN, } from '@umbraco-cms/backoffice/notification'; +import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; export type UsersViewType = 'list' | 'grid'; @customElement('umb-create-user-modal') export class UmbCreateUserModalElement extends UmbModalBaseElement { - - @query('#form') private _form!: HTMLFormElement; @state() - private _createdUser?: UserDetails; + private _createdUser?: UserResponseModel; - protected _userStore?: UmbUserStore; - private _notificationContext?: UmbNotificationContext; + @state() + private _createdUserInitialPassword?: string | null; + + #notificationContext?: UmbNotificationContext; + + // TODO: get from extension registry + #userRepository = new UmbUserRepository(this); connectedCallback(): void { super.connectedCallback(); this.consumeContext(UMB_NOTIFICATION_CONTEXT_TOKEN, (_instance) => { - this._notificationContext = _instance; - }); - - this.consumeContext(UMB_USER_STORE_CONTEXT_TOKEN, (_instance) => { - this._userStore = _instance; + this.#notificationContext = _instance; }); } - private _handleSubmit(e: Event) { + async #onSubmit(e: SubmitEvent) { e.preventDefault(); const form = e.target as HTMLFormElement; @@ -49,28 +49,34 @@ export class UmbCreateUserModalElement extends UmbModalBaseElement { const formData = new FormData(form); - console.log('formData', formData); - const name = formData.get('name') as string; const email = formData.get('email') as string; + //TODO: How should we handle pickers forms? const userGroupPicker = form.querySelector('#userGroups') as UmbInputPickerUserGroupElement; - const userGroups = userGroupPicker?.value || []; + const userGroups = userGroupPicker?.value || ['e5e7f6c8-7f9c-4b5b-8d5d-9e1e5a4f7e4d']; - this._userStore?.invite(name, email, '', userGroups).then((user) => { - if (user) { - this._createdUser = user; - } + // TODO: figure out when to use email or username + const { data } = await this.#userRepository.create({ + name, + email, + userName: email, + userGroupIds: userGroups, }); + + if (data) { + this._createdUser = data.user; + this._createdUserInitialPassword = data.createData.initialPassword; + } } - private _copyPassword() { + #copyPassword() { const passwordInput = this.shadowRoot?.querySelector('#password') as UUIInputPasswordElement; if (!passwordInput || typeof passwordInput.value !== 'string') return; navigator.clipboard.writeText(passwordInput.value); const data: UmbNotificationDefaultData = { message: 'Password copied' }; - this._notificationContext?.peek('positive', { data }); + this.#notificationContext?.peek('positive', { data }); } private _submitForm() { @@ -99,7 +105,7 @@ export class UmbCreateUserModalElement extends UmbModalBaseElement { can share with the user.

-
+ Name @@ -129,10 +135,10 @@ export class UmbCreateUserModalElement extends UmbModalBaseElement { id="password" label="password" name="password" - value="${'PUT GENERATED PASSWORD HERE'}" + value="${this._createdUserInitialPassword}" readonly> - +
`; } @@ -171,7 +177,7 @@ export class UmbCreateUserModalElement extends UmbModalBaseElement { `} `; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/create-user/create-user-modal.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/create-user/create-user-modal.test.ts deleted file mode 100644 index 3c38038129..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/create-user/create-user-modal.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -//TODO: Test has been commented out while we figure out how to setup import maps for the test environment -// import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; -// import UmbWorkspaceViewUsersCreateElement from './workspace-view-users-create.element'; - -// describe('UmbWorkspaceViewUsersCreateElement', () => { -// let element: UmbWorkspaceViewUsersCreateElement; -// beforeEach(async () => { -// element = await fixture(html``); -// }); - -// it('is defined with its own instance', () => { -// expect(element).to.be.instanceOf(UmbWorkspaceViewUsersCreateElement); -// }); - -// it('passes the a11y audit', async () => { -// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); -// }); -// }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/invite-user/invite-user-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/invite-user/invite-user-modal.element.ts index acd89430d0..463df6e6fe 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/invite-user/invite-user-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/invite-user/invite-user-modal.element.ts @@ -2,30 +2,21 @@ import { css, html, nothing } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, query, state } from 'lit/decorators.js'; import { UmbInputPickerUserGroupElement } from '../../../../shared/components/input-user-group/input-user-group.element'; -import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../repository/user.store'; +import { UmbUserRepository } from '../../repository/user.repository'; import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; -import type { UserDetails } from '@umbraco-cms/backoffice/models'; +import type { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; export type UsersViewType = 'list' | 'grid'; @customElement('umb-invite-user-modal') export class UmbInviteUserModalElement extends UmbModalBaseElement { - - @query('#form') private _form!: HTMLFormElement; @state() - private _invitedUser?: UserDetails; + private _invitedUser?: UserResponseModel; - protected _userStore?: UmbUserStore; - - connectedCallback(): void { - super.connectedCallback(); - - this.consumeContext(UMB_USER_STORE_CONTEXT_TOKEN, (usersContext) => { - this._userStore = usersContext; - }); - } + // TODO: get from extension registry + #userRepository = new UmbUserRepository(this); private _handleSubmit(e: Event) { e.preventDefault(); @@ -40,17 +31,29 @@ export class UmbInviteUserModalElement extends UmbModalBaseElement { const name = formData.get('name') as string; const email = formData.get('email') as string; + //TODO: How should we handle pickers forms? const userGroupPicker = form.querySelector('#userGroups') as UmbInputPickerUserGroupElement; - const userGroups = userGroupPicker?.value || []; + const userGroupIds = userGroupPicker?.value || []; const message = formData.get('message') as string; - this._userStore?.invite(name, email, message, userGroups).then((user) => { - if (user) { - this._invitedUser = user; - } + alert('implement invite'); + + // TODO: figure out when to use email or username + /* + const { data } = this.#userRepository.invite({ + name, + email, + userName: email, + message, + userGroupIds, }); + + if (data) { + this._invitedUser = data; + } + */ } private _submitForm() { @@ -144,7 +147,7 @@ export class UmbInviteUserModalElement extends UmbModalBaseElement { `} `; } - + static styles = [ UUITextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/invite-user/invite-user-modal.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/invite-user/invite-user-modal.test.ts deleted file mode 100644 index cf701e9fcc..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/invite-user/invite-user-modal.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -//TODO: Test has been commented out while we figure out how to setup import maps for the test environment -// import UmbWorkspaceViewUsersInviteElement from './workspace-view-users-invite.element'; -// import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// describe('UmbWorkspaceViewUsersInviteElement', () => { -// let element: UmbWorkspaceViewUsersInviteElement; -// beforeEach(async () => { -// element = await fixture(html``); -// }); - -// it('is defined with its own instance', () => { -// expect(element).to.be.instanceOf(UmbWorkspaceViewUsersInviteElement); -// }); - -// it('passes the a11y audit', async () => { -// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); -// }); -// }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.element.ts index 0f9d94eb2e..3b806610de 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.element.ts @@ -1,107 +1,98 @@ import { UUITextStyles } from '@umbraco-ui/uui-css'; import { css, html } from 'lit'; -import { customElement, state } from 'lit/decorators.js'; -import { UmbUserStore, UMB_USER_STORE_CONTEXT_TOKEN } from '../../repository/user.store'; -import { UmbModalElementPickerBase } from '@umbraco-cms/internal/modal'; -import type { UserDetails } from '@umbraco-cms/backoffice/models'; +import { customElement, property, state } from 'lit/decorators.js'; +import { UmbUserRepository } from '../../repository/user.repository'; +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; +import { UmbModalHandler, UmbUserPickerModalData, UmbUserPickerModalResult } from '@umbraco-cms/backoffice/modal'; +import { createExtensionClass, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extensions-api'; +import { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-user-picker-modal') -export class UmbUserPickerModalElement extends UmbModalElementPickerBase { - +export class UmbUserPickerModalElement extends UmbLitElement { + @property({ attribute: false }) + modalHandler?: UmbModalHandler; + + @property({ type: Object, attribute: false }) + data?: UmbUserPickerModalData; @state() - private _users: Array = []; + _selection: Array = []; - private _userStore?: UmbUserStore; + @state() + _multiple = false; - connectedCallback(): void { - super.connectedCallback(); - this.consumeContext(UMB_USER_STORE_CONTEXT_TOKEN, (userStore) => { - this._userStore = userStore; - this._observeUsers(); - }); + @state() + private _users: Array = []; + + #userRepository?: UmbUserRepository; + + constructor() { + super(); + + // TODO: this code is reused in multiple places, so it should be extracted to a function + new UmbObserverController( + this, + umbExtensionsRegistry.getByTypeAndAlias('repository', 'Umb.Repository.User'), + async (repositoryManifest) => { + if (!repositoryManifest) return; + + try { + const result = await createExtensionClass(repositoryManifest, [this]); + this.#userRepository = result; + this.#observeUsers(); + } catch (error) { + throw new Error('Could not create repository with alias: Umb.Repository.User'); + } + } + ); } - private _observeUsers() { - if (!this._userStore) return; - this.observe(this._userStore.getAll(), (users) => (this._users = users)); + async #observeUsers() { + if (!this.#userRepository) return; + // TODO is this the correct end point? + const { data } = await this.#userRepository.requestCollection(); + + if (data) { + this._users = data.items; + } + } + + #submit() { + this.modalHandler?.submit({ selection: this._selection }); + } + + #close() { + this.modalHandler?.reject(); } render() { return html` - + - -
-
- ${this._users.map( - (item) => html` -
this.handleSelection(item.id)} - @keydown=${(e: KeyboardEvent) => this._handleKeydown(e, item.id)} - class=${this.isSelected(item.id) ? 'item selected' : 'item'}> - - ${item.name} -
- ` - )} -
+ ${this._users.map( + (user) => html` + + + Hello + ` + )}
- - + +
-
+ `; } - + static styles = [ UUITextStyles, css` - uui-input { - width: 100%; - } - - hr { - border: none; - border-bottom: 1px solid var(--uui-color-divider); - margin: 16px 0; - } - #item-list { - display: flex; - flex-direction: column; - gap: var(--uui-size-1); - font-size: 1rem; - } - .item { - color: var(--uui-color-interactive); - display: flex; - align-items: center; - padding: var(--uui-size-2); - gap: var(--uui-size-space-5); - cursor: pointer; - position: relative; - border-radius: var(--uui-border-radius); - } - .item.selected { - background-color: var(--uui-color-selected); - color: var(--uui-color-selected-contrast); - } - .item:not(.selected):hover { - background-color: var(--uui-color-surface-emphasis); - color: var(--uui-color-interactive-emphasis); - } - .item.selected:hover { - background-color: var(--uui-color-selected-emphasis); - } - .item:hover uui-avatar { - border-color: var(--uui-color-surface-emphasis); - } - .item.selected uui-avatar { - border-color: var(--uui-color-selected-contrast); - } uui-avatar { border: 2px solid var(--uui-color-surface); + font-size: 12px; } `, ]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.test.ts deleted file mode 100644 index cfe1aad2a3..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/modals/user-picker/user-picker-modal.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { expect, fixture, html } from '@open-wc/testing'; -//TODO: Test has been commented out while we figure out how to setup import maps for the test environment -// import { UmbPickerLayoutUserElement } from './picker-layout-user.element'; -// import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; - -// describe('UmbPickerUserElement', () => { -// let element: UmbPickerLayoutUserElement; -// beforeEach(async () => { -// element = await fixture(html``); -// }); - -// it('is defined with its own instance', () => { -// expect(element).to.be.instanceOf(UmbPickerLayoutUserElement); -// }); - -// it('passes the a11y audit', async () => { -// await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); -// }); -// }); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/sources/user-collection.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/sources/user-collection.server.data.ts new file mode 100644 index 0000000000..b60b5cb840 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/sources/user-collection.server.data.ts @@ -0,0 +1,32 @@ +import { UmbCollectionDataSource } from '@umbraco-cms/backoffice/repository'; +import { UserResponseModel, UserResource } from '@umbraco-cms/backoffice/backend-api'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; +import type { UmbUserCollectionFilterModel } from '../../types'; + +/** + * A data source for the User that fetches data from the server + * @export + * @class UmbUserCollectionServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbUserCollectionServerDataSource implements UmbCollectionDataSource { + #host: UmbControllerHostElement; + + /** + * Creates an instance of UmbUserCollectionServerDataSource. + * @param {UmbControllerHostElement} host + * @memberof UmbUserCollectionServerDataSource + */ + constructor(host: UmbControllerHostElement) { + this.#host = host; + } + + getCollection() { + return tryExecuteAndNotify(this.#host, UserResource.getUser({})); + } + + filterCollection(filter: UmbUserCollectionFilterModel) { + return tryExecuteAndNotify(this.#host, UserResource.getUserFilter(filter)); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/sources/user.server.data.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/sources/user.server.data.ts new file mode 100644 index 0000000000..61c25ed1c6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/sources/user.server.data.ts @@ -0,0 +1,67 @@ +import { UmbUserDetailDataSource } from '../../types'; +import { DataSourceResponse } from '@umbraco-cms/backoffice/repository'; +import { + CreateUserRequestModel, + UpdateUserRequestModel, + UserPresentationBaseModel, + UserResource, + InviteUserRequestModel, +} from '@umbraco-cms/backoffice/backend-api'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the User that fetches data from the server + * @export + * @class UmbUserServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbUserServerDataSource implements UmbUserDetailDataSource { + #host: UmbControllerHostElement; + + /** + * Creates an instance of UmbUserServerDataSource. + * @param {UmbControllerHostElement} host + * @memberof UmbUserServerDataSource + */ + constructor(host: UmbControllerHostElement) { + this.#host = host; + } + + // Details + createScaffold(parentId: string | null): Promise> { + throw new Error('Method not implemented.'); + } + + get(id: string) { + if (!id) throw new Error('Id is missing'); + return tryExecuteAndNotify(this.#host, UserResource.getUserById({ id })); + } + + insert(data: CreateUserRequestModel) { + return tryExecuteAndNotify(this.#host, UserResource.postUser({ requestBody: data })); + } + + update(id: string, data: UpdateUserRequestModel) { + if (!id) throw new Error('Id is missing'); + + return tryExecuteAndNotify( + this.#host, + UserResource.putUserById({ + id, + requestBody: data, + }) + ); + } + + delete(id: string) { + if (!id) throw new Error('Id is missing'); + return tryExecuteAndNotify(this.#host, UserResource.deleteUserById({ id })); + } + + // Invite + invite(data: InviteUserRequestModel) { + if (!data) throw new Error('Invite data is missing'); + return tryExecuteAndNotify(this.#host, UserResource.postUserInvite({ requestBody: data })); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.repository.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.repository.ts index a9f65bc59d..3af4760d37 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.repository.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.repository.ts @@ -1,11 +1,131 @@ +import { UmbUserCollectionFilterModel, UmbUserDetailDataSource, UmbUserDetailRepository } from '../types'; +import { UMB_USER_STORE_CONTEXT_TOKEN, UmbUserStore } from './user.store'; +import { UmbUserServerDataSource } from './sources/user.server.data'; +import { UmbUserCollectionServerDataSource } from './sources/user-collection.server.data'; import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { UmbCollectionDataSource, UmbCollectionRepository } from '@umbraco-cms/backoffice/repository'; +import { + CreateUserRequestModel, + InviteUserRequestModel, + UpdateUserRequestModel, + UserResponseModel, +} from '@umbraco-cms/backoffice/backend-api'; +import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api'; +import { UMB_NOTIFICATION_CONTEXT_TOKEN, UmbNotificationContext } from '@umbraco-cms/backoffice/notification'; // TODO: implement -export class UmbUserRepository { +export class UmbUserRepository implements UmbUserDetailRepository, UmbCollectionRepository { #host: UmbControllerHostElement; + #detailSource: UmbUserDetailDataSource; + #detailStore?: UmbUserStore; + + #collectionSource: UmbCollectionDataSource; + + #notificationContext?: UmbNotificationContext; + constructor(host: UmbControllerHostElement) { this.#host = host; - console.log(this.#host); + + this.#detailSource = new UmbUserServerDataSource(this.#host); + this.#collectionSource = new UmbUserCollectionServerDataSource(this.#host); + + new UmbContextConsumerController(this.#host, UMB_USER_STORE_CONTEXT_TOKEN, (instance) => { + this.#detailStore = instance; + }); + + new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => { + this.#notificationContext = instance; + }); + } + + // COLLECTION + async requestCollection(filter: UmbUserCollectionFilterModel = { skip: 0, take: 100 }) { + //TODO: missing observable + return this.#collectionSource.filterCollection(filter); + } + + async filterCollection(filter: UmbUserCollectionFilterModel) { + return this.#collectionSource.filterCollection(filter); + } + + // DETAILS + createScaffold(parentId: string | null) { + if (parentId === undefined) throw new Error('Parent id is missing'); + return this.#detailSource.createScaffold(parentId); + } + + async requestById(id: string) { + if (!id) throw new Error('Id is missing'); + + const { data, error } = await this.#detailSource.get(id); + + if (data) { + this.#detailStore?.append(data); + } + + return { data, error }; + } + + async create(userRequestData: CreateUserRequestModel) { + if (!userRequestData) throw new Error('Data is missing'); + + const { data: createdData, error } = await this.#detailSource.insert(userRequestData); + + if (createdData && createdData.userId) { + const { data: user, error } = await this.#detailSource.get(createdData?.userId); + + if (user) { + this.#detailStore?.append(user); + + const notification = { data: { message: `User created` } }; + this.#notificationContext?.peek('positive', notification); + + const hello = { + user, + createData: createdData, + }; + + return { data: hello, error }; + } + } + + return { error }; + } + + async invite(inviteRequestData: InviteUserRequestModel) { + if (!inviteRequestData) throw new Error('Data is missing'); + const { data, error } = await this.#detailSource.invite(inviteRequestData); + } + + async save(id: string, user: UpdateUserRequestModel) { + if (!id) throw new Error('User id is missing'); + if (!user) throw new Error('User update data is missing'); + + const { data, error } = await this.#detailSource.update(id, user); + + if (data) { + this.#detailStore?.append(data); + + const notification = { data: { message: `User saved` } }; + this.#notificationContext?.peek('positive', notification); + } + + return { data, error }; + } + + async delete(id: string) { + if (!id) throw new Error('Id is missing'); + + const { error } = await this.#detailSource.delete(id); + + if (!error) { + this.#detailStore?.remove([id]); + + const notification = { data: { message: `User deleted` } }; + this.#notificationContext?.peek('positive', notification); + } + + return { error }; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.store.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.store.ts index fa14ef77fb..dc029ed4bd 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.store.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/repository/user.store.ts @@ -1,10 +1,8 @@ -import type { UserDetails } from '@umbraco-cms/backoffice/models'; -import { UmbArrayState, UmbNumberState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; -import { UmbEntityDetailStore, UmbStoreBase } from '@umbraco-cms/backoffice/store'; +import { UmbStoreBase } from '@umbraco-cms/backoffice/store'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; - -export type UmbUserStoreItemType = UserDetails; +import { UserResponseModel } from '@umbraco-cms/backoffice/backend-api'; export const UMB_USER_STORE_CONTEXT_TOKEN = new UmbContextToken('UmbUserStore'); @@ -14,291 +12,37 @@ export const UMB_USER_STORE_CONTEXT_TOKEN = new UmbContextToken('U * @extends {UmbStoreBase} * @description - Data Store for Users */ -export class UmbUserStore extends UmbStoreBase implements UmbEntityDetailStore { - public users = this._data.asObservable(); - - #totalUsers = new UmbNumberState(0); - public readonly totalUsers = this.#totalUsers.asObservable(); +export class UmbUserStore extends UmbStoreBase { + #data = new UmbArrayState([], (x) => x.id); constructor(host: UmbControllerHostElement) { - super(host, UMB_USER_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); - } - - getScaffold(entityType: string, parentId: string | null) { - return { - id: '', - name: '', - icon: '', - type: 'user', - hasChildren: false, - parentId: '', - email: '', - language: '', - status: 'enabled', - updateDate: '8/27/2022', - createDate: '9/19/2022', - failedLoginAttempts: 0, - userGroups: [], - contentStartNodes: [], - mediaStartNodes: [], - } as UserDetails; - } - - getAll() { - // TODO: use Fetcher API. - // TODO: only fetch if the data type is not in the store? - fetch(`/umbraco/backoffice/users/list/items`) - .then((res) => res.json()) - .then((data) => { - this.#totalUsers.next(data.total); - this._data.next(data.items); - }); - - return this.users; + super(host, UMB_USER_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState([], (x) => x.id)); } /** - * @description - Request a User by id. The User is added to the store and is returned as an Observable. - * @param {string} id - * @return {*} {(Observable)} - * @memberof UmbDataTypeStore + * Append a user to the store + * @param {UserResponseModel} user + * @memberof UmbUserStore */ - getByKey(id: string) { - // TODO: use Fetcher API. - // TODO: only fetch if the data type is not in the store? - fetch(`/umbraco/backoffice/users/details/${id}`) - .then((res) => res.json()) - .then((data) => { - this._data.appendOne(data); - }); - - return this._data.getObservablePart((users: Array) => - users.find((user: UmbUserStoreItemType) => user.id === id) - ); + append(user: UserResponseModel) { + this.#data.append([user]); } /** - * @description - Request Users by ids. - * @param {string} id - * @return {*} {(Observable)} - * @memberof UmbDataTypeStore + * Get a user by id + * @param {id} UserResponseModel id. + * @memberof UmbUserStore */ - getByKeys(ids: Array) { - const params = ids.map((id) => `id=${id}`).join('&'); - fetch(`/umbraco/backoffice/users/getByKeys?${params}`) - .then((res) => res.json()) - .then((data) => { - this._data.append(data); - }); - - return this._data.getObservablePart((users: Array) => - users.filter((user: UmbUserStoreItemType) => ids.includes(user.id)) - ); + byId(id: UserResponseModel['id']) { + return this.#data.getObservablePart((x) => x.find((y) => y.id === id)); } - getByName(name: string) { - name = name.trim(); - name = name.toLocaleLowerCase(); - - const params = `name=${name}`; - fetch(`/umbraco/backoffice/users/getByName?${params}`) - .then((res) => res.json()) - .then((data) => { - this._data.append(data); - }); - - return this._data.getObservablePart((users: Array) => - users.filter((user: UmbUserStoreItemType) => user.name.toLocaleLowerCase().includes(name)) - ); + /** + * Removes data-types in the store with the given uniques + * @param {string[]} uniques + * @memberof UmbUserStore + */ + remove(uniques: Array) { + this.#data.remove(uniques); } - - async enableUsers(userKeys: Array) { - // TODO: use Fetcher API. - try { - const res = await fetch('/umbraco/backoffice/users/enable', { - method: 'POST', - body: JSON.stringify(userKeys), - headers: { - 'Content-Type': 'application/json', - }, - }); - const enabledKeys = await res.json(); - const storedUsers = this._data.getValue().filter((user) => enabledKeys.includes(user.id)); - - storedUsers.forEach((user) => { - user.status = 'enabled'; - }); - - this._data.append(storedUsers); - } catch (error) { - console.error('Enable Users failed', error); - } - } - - async updateUserGroup(userKeys: Array, userGroup: string) { - // TODO: use Fetcher API. - try { - const res = await fetch('/umbraco/backoffice/users/updateUserGroup', { - method: 'POST', - body: JSON.stringify({ userKeys, userGroup }), - headers: { - 'Content-Type': 'application/json', - }, - }); - const enabledKeys = await res.json(); - const storedUsers = this._data.getValue().filter((user) => enabledKeys.includes(user.id)); - - storedUsers.forEach((user) => { - if (userKeys.includes(user.id)) { - user.userGroups.push(userGroup); - } else { - user.userGroups = user.userGroups.filter((group: any) => group !== userGroup); - } - }); - - this._data.append(storedUsers); - } catch (error) { - console.error('Add user group failed', error); - } - } - - async removeUserGroup(userKeys: Array, userGroup: string) { - // TODO: use Fetcher API. - try { - const res = await fetch('/umbraco/backoffice/users/enable', { - method: 'POST', - body: JSON.stringify({ userKeys, userGroup }), - headers: { - 'Content-Type': 'application/json', - }, - }); - const enabledKeys = await res.json(); - const storedUsers = this._data.getValue().filter((user) => enabledKeys.includes(user.id)); - - storedUsers.forEach((user) => { - user.userGroups = user.userGroups.filter((group: any) => group !== userGroup); - }); - - this._data.append(storedUsers); - } catch (error) { - console.error('Remove user group failed', error); - } - } - - async disableUsers(userKeys: Array) { - // TODO: use Fetcher API. - try { - const res = await fetch('/umbraco/backoffice/users/disable', { - method: 'POST', - body: JSON.stringify(userKeys), - headers: { - 'Content-Type': 'application/json', - }, - }); - const disabledKeys = await res.json(); - const storedUsers = this._data.getValue().filter((user) => disabledKeys.includes(user.id)); - - storedUsers.forEach((user) => { - user.status = 'disabled'; - }); - - this._data.append(storedUsers); - } catch (error) { - console.error('Disable Users failed', error); - } - } - - async deleteUsers(userKeys: Array) { - // TODO: use Fetcher API. - try { - const res = await fetch('/umbraco/backoffice/users/delete', { - method: 'POST', - body: JSON.stringify(userKeys), - headers: { - 'Content-Type': 'application/json', - }, - }); - const deletedKeys = await res.json(); - this._data.remove(deletedKeys); - } catch (error) { - console.error('Delete Users failed', error); - } - } - - async save(users: Array) { - // TODO: use Fetcher API. - try { - const res = await fetch('/umbraco/backoffice/users/save', { - method: 'POST', - body: JSON.stringify(users), - headers: { - 'Content-Type': 'application/json', - }, - }); - const json = await res.json(); - this._data.append(json); - } catch (error) { - console.error('Save user error', error); - } - } - - async invite(name: string, email: string, message: string, userGroups: Array) { - // TODO: use Fetcher API. - try { - const res = await fetch('/umbraco/backoffice/users/invite', { - method: 'POST', - body: JSON.stringify({ name, email, message, userGroups }), - headers: { - 'Content-Type': 'application/json', - }, - }); - const json = (await res.json()) as UmbUserStoreItemType[]; - this._data.append(json); - return json[0]; - } catch (error) { - console.error('Invite user error', error); - } - - return null; - } - - // public updateUser(user: UserItem) { - // const users = this._users.getValue(); - // const index = users.findIndex((u) => u.id === user.id); - // if (index === -1) return; - // users[index] = { ...users[index], ...user }; - // console.log('updateUser', user, users[index]); - // this._users.next(users); - // this.requestUpdate('users'); - // } - - // public inviteUser(name: string, email: string, userGroup: string, message: string): UserItem { - // const users = this._users.getValue(); - // const user = { - // id: this._users.getValue().length + 1, - // id: uuidv4(), - // name: name, - // email: email, - // status: 'invited', - // language: 'en', - // updateDate: new Date().toISOString(), - // createDate: new Date().toISOString(), - // failedLoginAttempts: 0, - // userGroup: userGroup, - // }; - // this._users.next([...users, user]); - // this.requestUpdate('users'); - - // //TODO: Send invite email with message - // return user; - // } - - // public deleteUser(id: string) { - // const users = this._users.getValue(); - // const index = users.findIndex((u) => u.id === id); - // if (index === -1) return; - // users.splice(index, 1); - // this._users.next(users); - // this.requestUpdate('users'); - // } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/section-view/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/section-view/manifests.ts new file mode 100644 index 0000000000..082ff5fbc1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/section-view/manifests.ts @@ -0,0 +1,22 @@ +import { UMB_USER_SECTION_ALIAS } from '../../user-section/manifests'; +import type { ManifestSectionView } from '@umbraco-cms/backoffice/extensions-registry'; + +const sectionsViews: Array = [ + { + type: 'sectionView', + alias: 'Umb.SectionView.Users', + name: 'Users Section View', + loader: () => import('./users-section-view.element'), + weight: 200, + meta: { + label: 'Users', + pathname: 'users', + icon: 'umb:user', + }, + conditions: { + sections: [UMB_USER_SECTION_ALIAS], + }, + }, +]; + +export const manifests = [...sectionsViews]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/section-view/users-section-view.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/section-view/users-section-view.element.ts new file mode 100644 index 0000000000..808861453c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/section-view/users-section-view.element.ts @@ -0,0 +1,86 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement } from 'lit/decorators.js'; +import type { IRoute } from '@umbraco-cms/backoffice/router'; + +import '../collection/views/table/user-table-collection-view.element'; +import '../collection/views/grid/user-grid-collection-view.element'; + +import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; + +@customElement('umb-section-view-users') +export class UmbSectionViewUsersElement extends UmbLitElement { + static styles = [ + UUITextStyles, + css` + :host { + height: 100%; + } + + #router-slot { + height: calc(100% - var(--umb-header-layout-height)); + } + `, + ]; + + #routes: IRoute[] = [ + { + path: 'collection', + component: () => import('../collection/user-collection.element'), + }, + { + path: 'user', + component: () => import('../workspace/user-workspace.element'), + }, + { + path: '**', + redirectTo: 'collection', + }, + ]; + + // // TODO: This must be turned into context api: Maybe its a Collection View (SectionView Collection View)? + // private _userStore?: UmbUserStore; + + // #users = new DeepState(>[]); + // public readonly users = this.#users.asObservable(); + + // #search = new DeepState(''); + // public readonly search = this.#search.asObservable(); + + // constructor() { + // super(); + + // this.consumeContext(UMB_USER_STORE_CONTEXT_TOKEN, (_instance) => { + // this._userStore = _instance; + // this._observeUsers(); + // }); + // } + + // private _observeUsers() { + // if (!this._userStore) return; + + // if (this.#search.getValue()) { + // this.observe(this._userStore.getByName(this.#search.getValue()), (users) => this.#users.next(users)); + // } else { + // this.observe(this._userStore.getAll(), (users) => this.#users.next(users)); + // } + // } + + // public setSearch(value?: string) { + // this.#search.next(value || ''); + // this._observeUsers(); + // this.requestUpdate('search'); + // } + + render() { + return html``; + } +} + +export default UmbSectionViewUsersElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-section-view-users': UmbSectionViewUsersElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/types.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/types.ts new file mode 100644 index 0000000000..47d72404da --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/types.ts @@ -0,0 +1,41 @@ +import type { + CreateUserRequestModel, + CreateUserResponseModel, + DirectionModel, + InviteUserRequestModel, + UpdateUserRequestModel, + UserOrderModel, + UserResponseModel, + UserStateModel, +} from '@umbraco-cms/backoffice/backend-api'; +import { UmbDataSource, UmbDetailRepository } from '@umbraco-cms/backoffice/repository'; + +export interface UmbCreateUserResponseModel { + user: UserResponseModel; + createData: CreateUserResponseModel; +} + +export interface UmbUserCollectionFilterModel { + skip?: number; + take?: number; + orderBy?: UserOrderModel; + orderDirection?: DirectionModel; + userGroupIds?: string[]; + userStates?: UserStateModel[]; + filter?: string; +} + +export interface UmbUserDetailDataSource + extends UmbDataSource { + invite(data: InviteUserRequestModel): Promise; +} + +export interface UmbUserDetailRepository + extends UmbDetailRepository< + CreateUserRequestModel, + UmbCreateUserResponseModel, + UpdateUserRequestModel, + UserResponseModel + > { + invite(data: InviteUserRequestModel): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/workspace-action-user-save.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/user-workspace-action-save.element.ts similarity index 84% rename from src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/workspace-action-user-save.element.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/user-workspace-action-save.element.ts index 505187cf1d..15e4700487 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/workspace-action-user-save.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/actions/user-workspace-action-save.element.ts @@ -6,10 +6,8 @@ import { UmbUserWorkspaceContext } from '../user-workspace.context'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { UMB_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/context-api'; -@customElement('umb-workspace-action-user-save') -export class UmbWorkspaceActionUserSaveElement extends UmbLitElement { - - +@customElement('umb-user-workspace-action-save') +export class UmbUserWorkspaceActionSaveElement extends UmbLitElement { @state() private _saveButtonState?: UUIButtonState; @@ -45,14 +43,14 @@ export class UmbWorkspaceActionUserSaveElement extends UmbLitElement { label="save" .state="${this._saveButtonState}">`; } - + static styles = [UUITextStyles, css``]; } -export default UmbWorkspaceActionUserSaveElement; +export default UmbUserWorkspaceActionSaveElement; declare global { interface HTMLElementTagNameMap { - 'umb-workspace-action-user-save': UmbWorkspaceActionUserSaveElement; + 'umb-user-workspace-action-save': UmbUserWorkspaceActionSaveElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace-edit.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace-edit.element.ts index a4b7fd3b18..ea2a0aba75 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace-edit.element.ts @@ -6,162 +6,115 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { repeat } from 'lit/directives/repeat.js'; import { UmbCurrentUserStore, UMB_CURRENT_USER_STORE_CONTEXT_TOKEN } from '../../current-user/current-user.store'; +import { getLookAndColorFromUserStatus } from '../../utils'; import { UmbUserWorkspaceContext } from './user-workspace.context'; import { UMB_CHANGE_PASSWORD_MODAL } from '@umbraco-cms/backoffice/modal'; import type { UmbModalContext } from '@umbraco-cms/backoffice/modal'; -import { getLookAndColorFromUserStatus } from '@umbraco-cms/backoffice/utils'; import type { UserDetails } from '@umbraco-cms/backoffice/models'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; -import '../../../shared/components/input-user-group/input-user-group.element'; -import '../../../shared/property-editors/uis/document-picker/property-editor-ui-document-picker.element'; -import '../../../shared/components/workspace/workspace-layout/workspace-layout.element'; +import { UMB_ENTITY_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/context-api'; +import { UserResponseModel, UserStateModel } from '@umbraco-cms/backoffice/backend-api'; @customElement('umb-user-workspace-edit') export class UmbUserWorkspaceEditElement extends UmbLitElement { - - @state() private _currentUser?: UserDetails; - private _currentUserStore?: UmbCurrentUserStore; - private _modalContext?: UmbModalContext; - - private _languages = []; //TODO Add languages - - private _workspaceContext: UmbUserWorkspaceContext = new UmbUserWorkspaceContext(this); - @state() - private _user?: UserDetails; + private _user?: UserResponseModel; - @state() - private _userName = ''; + #currentUserStore?: UmbCurrentUserStore; + #modalContext?: UmbModalContext; + #languages = []; //TODO Add languages + #workspaceContext?: UmbUserWorkspaceContext; constructor() { super(); this.consumeContext(UMB_CURRENT_USER_STORE_CONTEXT_TOKEN, (store) => { - this._currentUserStore = store; - this._observeCurrentUser(); + this.#currentUserStore = store; + this.#observeCurrentUser(); }); - this.observe(this._workspaceContext.data, (user) => { - // TODO: fix type mismatch: - this._user = user as any; - if (user && user.name !== this._userName) { - this._userName = user.name || ''; - } + this.consumeContext(UMB_ENTITY_WORKSPACE_CONTEXT, (workspaceContext) => { + this.#workspaceContext = workspaceContext as UmbUserWorkspaceContext; + this.#observeUser(); }); } - private async _observeCurrentUser() { - if (!this._currentUserStore) return; - - // TODO: do not have static current user service, we need to make a ContextAPI for this. - this.observe(this._currentUserStore.currentUser, (currentUser) => { - this._currentUser = currentUser; - }); + #observeUser() { + if (!this.#workspaceContext) return; + this.observe(this.#workspaceContext.data, (user) => (this._user = user)); } - private _updateUserStatus() { - if (!this._user || !this._workspaceContext) return; - - const isDisabled = this._user.status === 'disabled'; - // TODO: make sure we use the workspace for this: - /* - isDisabled - ? this._workspaceContext.getStore()?.enableUsers([this._user.id]) - : this._workspaceContext.getStore()?.disableUsers([this._user.id]); - */ + #observeCurrentUser() { + if (!this.#currentUserStore) return; + this.observe(this.#currentUserStore.currentUser, (currentUser) => (this._currentUser = currentUser)); } - private _deleteUser() { - if (!this._user || !this._workspaceContext) return; + #onUserStatusChange() { + if (!this._user) return; - // TODO: make sure we use the workspace for this: - //this._workspaceContext.getStore()?.deleteUsers([this._user.id]); + if (this._user.state === UserStateModel.ACTIVE) { + //TODO: This should go directly to the user repository instead of the context so that the request is send immediately. + this.#workspaceContext?.updateProperty('state', UserStateModel.DISABLED); + } - history.pushState(null, '', 'section/users/view/users/overview'); + if (this._user.state === UserStateModel.DISABLED) { + //TODO: This should go directly to the user repository instead of the context so that the request is send immediately. + this.#workspaceContext?.updateProperty('state', UserStateModel.ACTIVE); + } + } + + #onUserDelete() { + //TODO: Delete user and redirect to user list. } // TODO. find a way where we don't have to do this for all workspaces. - private _handleInput(event: UUIInputEvent) { + #onNameChange(event: UUIInputEvent) { if (event instanceof UUIInputEvent) { const target = event.composedPath()[0] as UUIInputElement; if (typeof target?.value === 'string') { - this._updateProperty('name', target.value); + this.#workspaceContext?.updateProperty('name', target.value); } } } - private _updateProperty(propertyName: string, value: unknown) { - this._workspaceContext?.update({ [propertyName]: value }); - } - - private _renderContentStartNodes() { - if (!this._user) return; - - if (this._user.contentStartNodes.length < 1) - return html` - - - - `; - - //TODO Render the name of the content start node instead of it's id. - return repeat( - this._user.contentStartNodes, - (node) => node, - (node) => { - return html` - - - - `; - } - ); - } - - private _changePassword() { - this._modalContext?.open(UMB_CHANGE_PASSWORD_MODAL, { - requireOldPassword: this._currentUserStore?.isAdmin === false, + #onPasswordChange() { + // TODO: check if current user is admin + this.#modalContext?.open(UMB_CHANGE_PASSWORD_MODAL, { + requireOldPassword: false, }); } - private _renderActionButtons() { - if (!this._user) return; + render() { + if (!this._user) return html`User not found`; - const buttons: TemplateResult[] = []; - - if (this._currentUserStore?.isAdmin === false) return nothing; - - if (this._user?.status !== 'invited') - buttons.push( - html` - - ` - ); - - if (this._currentUser?.id !== this._user?.id) - buttons.push(html` `); - - buttons.push( - html` ` - ); - - return buttons; + return html` + + ${this.#renderHeader()} +
+
${this.#renderLeftColumn()}
+
${this.#renderRightColumn()}
+
+
+ `; } - private _renderLeftColumn() { + #renderHeader() { + return html` + + `; + } + + #renderLeftColumn() { if (!this._user) return nothing; return html` @@ -170,7 +123,7 @@ export class UmbUserWorkspaceEditElement extends UmbLitElement { - + @@ -179,15 +132,17 @@ export class UmbUserWorkspaceEditElement extends UmbLitElement { this._updateProperty('userGroups', e.target.value)}> + .value=${this._user.userGroupIds ?? []} + @change=${(e: any) => + this.#workspaceContext?.updateProperty('userGroupIds', e.target.value)}> this._updateProperty('contentStartNodes', e.target.value)} + .value=${this._user.contentStartNodeIds} + @property-value-change=${(e: any) => + this.#workspaceContext?.updateProperty('contentStartNodeIds', e.target.value)} slot="editor"> Content - ${this._renderContentStartNodes()} + ${this.#renderContentStartNodes()}
Media @@ -212,75 +167,116 @@ export class UmbUserWorkspaceEditElement extends UmbLitElement {
`; } - private _renderRightColumn() { - if (!this._user || !this._workspaceContext) return nothing; + #renderRightColumn() { + if (!this._user || !this.#workspaceContext) return nothing; - const statusLook = getLookAndColorFromUserStatus(this._user.status); + const statusLook = getLookAndColorFromUserStatus(this._user.state); return html`

- ${this._renderActionButtons()} + ${this.#renderActionButtons()} +
Status: - ${this._user.status} + ${this._user.state}
- ${this._user?.status === 'invited' + + ${this._user?.state === UserStateModel.INVITED ? html` ` : nothing} -
- Last login: - ${this._user.lastLoginDate || `${this._user.name} has not logged in yet`} -
-
- Failed login attempts - ${this._user.failedLoginAttempts} -
-
- Last lockout date: - ${this._user.lastLockoutDate || `${this._user.name} has not been locked out`} -
-
- Password last changed: - ${this._user.lastLoginDate || `${this._user.name} has not changed password`} -
-
- User created: - ${this._user.createDate} -
-
- User last updated: - ${this._user.updateDate} -
-
- Key: - ${this._user.id} -
+ ${this.#renderInfoItem('Last login', this._user.lastLoginDate || `${this._user.name} has not logged in yet`)} + ${this.#renderInfoItem('Failed login attempts', this._user.failedLoginAttempts)} + ${this.#renderInfoItem( + 'Last lockout date', + this._user.lastlockoutDate || `${this._user.name} has not been locked out` + )} + ${this.#renderInfoItem( + 'Password last changed', + this._user.lastLoginDate || `${this._user.name} has not changed password` + )} + ${this.#renderInfoItem('User created', this._user.createDate)} + ${this.#renderInfoItem('User last updated', this._user.updateDate)} + ${this.#renderInfoItem('Key', this._user.id)}
`; } - render() { - if (!this._user) return html`User not found`; - + #renderInfoItem(label: string, value?: string | number) { return html` - - -
-
${this._renderLeftColumn()}
-
${this._renderRightColumn()}
-
-
+
+ ${label} + ${value} +
`; } - + + #renderActionButtons() { + if (!this._user) return nothing; + + //TODO: Find out if the current user is an admin. If not, show no buttons. + // if (this._currentUserStore?.isAdmin === false) return nothing; + + const buttons: TemplateResult[] = []; + + if (this._user.state !== UserStateModel.INVITED) { + const button = html` + + `; + + buttons.push(button); + } + + if (this._currentUser?.id !== this._user?.id) { + const button = html` + + `; + + buttons.push(button); + } + + buttons.push( + html`` + ); + + return buttons; + } + + #renderContentStartNodes() { + if (!this._user || !this._user.contentStartNodeIds) return; + + if (this._user.contentStartNodeIds.length < 1) + return html` + + + + `; + + //TODO Render the name of the content start node instead of it's id. + return repeat( + this._user.contentStartNodeIds, + (node) => node, + (node) => { + return html` + + + + `; + } + ); + } + static styles = [ UUITextStyles, css` @@ -289,6 +285,13 @@ export class UmbUserWorkspaceEditElement extends UmbLitElement { height: 100%; } + #header { + width: 100%; + display: grid; + grid-template-columns: var(--uui-size-layout-1) 1fr; + padding: var(--uui-size-layout-1); + } + #main { display: grid; grid-template-columns: 1fr 350px; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts index 66d6589d2c..b305057f64 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.context.ts @@ -1,55 +1,71 @@ -import { UMB_USER_STORE_CONTEXT_TOKEN } from '../repository/user.store'; import { UmbWorkspaceContext } from '../../../shared/components/workspace/workspace-context/workspace-context'; -import { UmbEntityWorkspaceManager } from '../../../shared/components/workspace/workspace-context/entity-manager-controller'; import { UmbUserRepository } from '../repository/user.repository'; import { UmbEntityWorkspaceContextInterface } from '@umbraco-cms/backoffice/workspace'; -import type { UserDetails } from '@umbraco-cms/backoffice/models'; import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller'; +import { + UpdateUserGroupRequestModel, + UpdateUserRequestModel, + UserResponseModel, +} from '@umbraco-cms/backoffice/backend-api'; +import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; export class UmbUserWorkspaceContext - extends UmbWorkspaceContext - implements UmbEntityWorkspaceContextInterface + extends UmbWorkspaceContext + implements UmbEntityWorkspaceContextInterface { - #manager = new UmbEntityWorkspaceManager( - this.host, - 'user', - UMB_USER_STORE_CONTEXT_TOKEN - ); - - public readonly data = this.#manager.state.asObservable(); - public readonly name = this.#manager.state.getObservablePart((state) => state?.name); - - // TODO: remove this magic connection, instead create the necessary methods to update parts. - update = this.#manager.state.update; - constructor(host: UmbControllerHostElement) { super(host, new UmbUserRepository(host)); } - setName(name: string) { - this.#manager.state.update({ name: name }); - } - getEntityType = this.#manager.getEntityType; - getUnique = this.#manager.getEntityKey; - getEntityId = this.#manager.getEntityKey; - getStore = this.#manager.getStore; - getData = this.#manager.getData as any; // TODO: fix type mismatch, this will mos likely be handled when switching to repositories. - load = this.#manager.load; - create = this.#manager.create; - save = this.#manager.save; - destroy = this.#manager.destroy; + #data = new UmbObjectState(undefined); + data = this.#data.asObservable(); - getName() { - throw new Error('getName is not implemented for UmbWorkspaceUserContext'); + async load(id: string) { + console.log('load'); + + const { data } = await this.repository.requestById(id); + if (data) { + this.setIsNew(false); + this.#data.update(data); + } } - propertyValueByAlias(alias: string) { - throw new Error('setPropertyValue is not implemented for UmbWorkspaceUserContext'); + getEntityId(): string | undefined { + return this.getData()?.id || ''; } - getPropertyValue(alias: string) { - throw new Error('setPropertyValue is not implemented for UmbWorkspaceUserContext'); + + getEntityType(): string { + return 'user'; } - setPropertyValue(alias: string, value: unknown) { - throw new Error('setPropertyValue is not implemented for UmbWorkspaceUserContext'); + + getData() { + return this.#data.getValue(); + } + + updateProperty( + propertyName: PropertyName, + value: UserResponseModel[PropertyName] + ) { + this.#data.update({ [propertyName]: value }); + } + + async save() { + if (!this.#data.value) return; + if (!this.#data.value.id) return; + + console.log('save', this.#data.value, this.getIsNew()); + + if (this.getIsNew()) { + await this.repository.create(this.#data.value); + } else { + //TODO: temp hack: why does the response model allow for nulls but not the request model? + await this.repository.save(this.#data.value.id, this.#data.value as UpdateUserRequestModel); + } + // If it went well, then its not new anymore?. + this.setIsNew(false); + } + + destroy(): void { + this.#data.complete(); } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts index 070d1a03dd..20d2bcfd72 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/users/workspace/user-workspace.element.ts @@ -1,10 +1,10 @@ import { html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, state } from 'lit/decorators.js'; -import { UmbUserWorkspaceContext } from './user-workspace.context'; -import { UmbUserWorkspaceEditElement } from './user-workspace-edit.element'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import type { IRoute } from '@umbraco-cms/backoffice/router'; +import { UmbUserWorkspaceContext } from './user-workspace.context'; +import { UmbUserWorkspaceEditElement } from './user-workspace-edit.element'; import '../../../shared/components/input-user-group/input-user-group.element'; import '../../../shared/property-editors/uis/document-picker/property-editor-ui-document-picker.element'; @@ -12,15 +12,13 @@ import '../../../shared/components/workspace/workspace-layout/workspace-layout.e @customElement('umb-user-workspace') export class UmbUserWorkspaceElement extends UmbLitElement { - - #workspaceContext = new UmbUserWorkspaceContext(this); #element = new UmbUserWorkspaceEditElement(); @state() _routes: IRoute[] = [ { - path: 'edit/:id', + path: ':id', component: () => this.#element, setup: (component, info) => { const id = info.match.params.id; @@ -32,7 +30,7 @@ export class UmbUserWorkspaceElement extends UmbLitElement { render() { return html` `; } - + static styles = [UUITextStyles]; } diff --git a/src/Umbraco.Web.UI.Client/libs/utils/utils.test.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/utils.test.ts similarity index 52% rename from src/Umbraco.Web.UI.Client/libs/utils/utils.test.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/utils.test.ts index 36bbe2e2a0..56e8f0f197 100644 --- a/src/Umbraco.Web.UI.Client/libs/utils/utils.test.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/utils.test.ts @@ -1,15 +1,15 @@ import { expect } from '@open-wc/testing'; import { InterfaceColor, InterfaceLook } from '@umbraco-ui/uui-base/lib/types'; import { getLookAndColorFromUserStatus } from './utils'; -import type { UserStatus } from '@umbraco-cms/backoffice/models'; +import { UserStateModel } from '@umbraco-cms/backoffice/backend-api'; describe('UmbUserExtensions', () => { it('returns correct look and color from a status string', () => { - const testCases: { status: UserStatus; look: InterfaceLook; color: InterfaceColor }[] = [ - { status: 'enabled', look: 'primary', color: 'positive' }, - { status: 'inactive', look: 'primary', color: 'warning' }, - { status: 'invited', look: 'primary', color: 'warning' }, - { status: 'disabled', look: 'primary', color: 'danger' }, + const testCases: { status: UserStateModel; look: InterfaceLook; color: InterfaceColor }[] = [ + { status: UserStateModel.ACTIVE, look: 'primary', color: 'positive' }, + { status: UserStateModel.INACTIVE, look: 'primary', color: 'warning' }, + { status: UserStateModel.INVITED, look: 'primary', color: 'warning' }, + { status: UserStateModel.DISABLED, look: 'primary', color: 'danger' }, ]; testCases.forEach((testCase) => { diff --git a/src/Umbraco.Web.UI.Client/libs/utils/utils.ts b/src/Umbraco.Web.UI.Client/src/backoffice/users/utils.ts similarity index 55% rename from src/Umbraco.Web.UI.Client/libs/utils/utils.ts rename to src/Umbraco.Web.UI.Client/src/backoffice/users/utils.ts index 76da7bba7c..18bfa29ba1 100644 --- a/src/Umbraco.Web.UI.Client/libs/utils/utils.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/users/utils.ts @@ -1,14 +1,16 @@ import { InterfaceColor, InterfaceLook } from '@umbraco-ui/uui-base/lib/types'; -import type { UserStatus } from '@umbraco-cms/backoffice/models'; +import { UserStateModel } from '@umbraco-cms/backoffice/backend-api'; -export const getLookAndColorFromUserStatus = (status: UserStatus): { look: InterfaceLook; color: InterfaceColor } => { - switch ((status || '').toLowerCase()) { - case 'invited': - case 'inactive': +export const getLookAndColorFromUserStatus = ( + status?: UserStateModel +): { look: InterfaceLook; color: InterfaceColor } => { + switch (status) { + case UserStateModel.INACTIVE: + case UserStateModel.INVITED: return { look: 'primary', color: 'warning' }; - case 'enabled': + case UserStateModel.ACTIVE: return { look: 'primary', color: 'positive' }; - case 'disabled': + case UserStateModel.DISABLED: return { look: 'primary', color: 'danger' }; default: return { look: 'secondary', color: 'default' }; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/users.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/users.data.ts index 740b6a1957..dc758d954f 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/users.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/users.data.ts @@ -1,2375 +1,179 @@ -import { UmbEntityData } from './entity.data'; -import type { UserDetails } from '@umbraco-cms/backoffice/models'; +import { UmbData } from './data'; +import { PagedUserResponseModel, UserResponseModel, UserStateModel } from '@umbraco-cms/backoffice/backend-api'; // Temp mocked database -class UmbUsersData extends UmbEntityData { - constructor(data: UserDetails[]) { +class UmbUsersData extends UmbData { + constructor(data: UserResponseModel[]) { super(data); } - getAll() { - return this.data; + getAll(): PagedUserResponseModel { + return { + total: this.data.length, + items: this.data, + }; } - updateUserGroup(ids: string[], userGroup: string) { - this.data.forEach((user) => { - if (ids.includes(user.id)) { - user.userGroups = [...user.userGroups, userGroup]; - } else { - user.userGroups = user.userGroups.filter((group) => group !== userGroup); + getById(id: string): UserResponseModel | undefined { + return this.data.find((user) => user.id === id); + } + + save(saveItem: UserResponseModel) { + const foundIndex = this.data.findIndex((item) => item.id === saveItem.id); + if (foundIndex !== -1) { + // update + this.data[foundIndex] = saveItem; + this.updateData(saveItem); + } else { + // new + this.data.push(saveItem); + } + + return saveItem; + } + + protected updateData(updateItem: UserResponseModel) { + const itemIndex = this.data.findIndex((item) => item.id === updateItem.id); + const item = this.data[itemIndex]; + + console.log('updateData', updateItem, itemIndex, item); + + if (!item) return; + + const itemKeys = Object.keys(item); + const newItem = {}; + + for (const [key] of Object.entries(updateItem)) { + if (itemKeys.indexOf(key) !== -1) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + newItem[key] = updateItem[key]; } + } - this.updateData(user); - }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.data[itemIndex] = newItem; - return this.data.map((user) => user.id); + console.log('updateData', this.data[itemIndex]); } - enable(ids: string[]) { - const users = this.data.filter((user) => ids.includes(user.id)); - users.forEach((user) => { - user.status = 'enabled'; - this.updateData(user); - }); - return users.map((user) => user.id); - } + // updateUserGroup(ids: string[], userGroup: string) { + // this.data.forEach((user) => { + // if (ids.includes(user.id)) { + // } else { + // } - disable(ids: string[]) { - const users = this.data.filter((user) => ids.includes(user.id)); - users.forEach((user) => { - user.status = 'disabled'; - this.updateData(user); - }); - return users.map((user) => user.id); - } + // this.updateData(user); + // }); + + // return this.data.map((user) => user.id); + // } + + // enable(ids: string[]) { + // const users = this.data.filter((user) => ids.includes(user.id)); + // users.forEach((user) => { + // user.status = 'enabled'; + // this.updateData(user); + // }); + // return users.map((user) => user.id); + // } + + // disable(ids: string[]) { + // const users = this.data.filter((user) => ids.includes(user.id)); + // users.forEach((user) => { + // user.status = 'disabled'; + // this.updateData(user); + // }); + // return users.map((user) => user.id); + // } } -export const data: Array = [ - { - id: 'a953e376-89f8-46d1-bed9-1b47743aa38a', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Lasse Fredslund', - email: 'lrozycki0@technorati.com', - language: 'Bosnian', - status: 'invited', - lastLoginDate: '10/8/2022', - lastLockoutDate: '10/4/2022', - lastPasswordChangeDate: '3/7/2022', - updateDate: '12/1/2021', - createDate: '4/14/2022', - failedLoginAttempts: 895, - userGroups: ['397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1'], - }, +export const data: Array = [ { id: 'bca6c733-a63d-4353-a271-9a8b6bcca8bd', type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', + $type: 'UserResponseModel', + contentStartNodeIds: [], + mediaStartNodeIds: [], name: 'Erny Baptista', email: 'ebaptista1@csmonitor.com', - language: 'Kannada', - status: 'disabled', + languageIsoCode: 'Kannada', + state: UserStateModel.ACTIVE, lastLoginDate: '9/10/2022', - lastLockoutDate: '11/23/2021', + lastlockoutDate: '11/23/2021', lastPasswordChangeDate: '1/10/2022', updateDate: '2/10/2022', createDate: '3/13/2022', failedLoginAttempts: 946, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], }, { - id: '9f63996f-71e9-49be-bc21-5a69ea97e72e', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Boris Shovel', - email: 'bshovel2@pen.io', - language: 'Tajik', - status: 'invited', - lastLoginDate: '9/15/2022', - lastLockoutDate: '1/13/2022', - lastPasswordChangeDate: '11/7/2021', - updateDate: '9/20/2022', - createDate: '12/19/2021', - failedLoginAttempts: 915, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: 'ff1d1bff-b6d2-444b-950a-68b5eec46277', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Emalee Danbye', - email: 'edanbye3@economist.com', - language: 'Dari', - status: 'disabled', - lastLoginDate: '11/28/2021', - lastLockoutDate: '8/26/2022', - lastPasswordChangeDate: '10/1/2022', - updateDate: '2/27/2022', - createDate: '4/13/2022', - failedLoginAttempts: 728, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: 'c9cf849f-0536-4e38-a91a-02c8c45a6f47', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Fidel Bastiman', - email: 'fbastiman4@sitemeter.com', - language: 'Dari', - status: 'invited', - lastLoginDate: '9/22/2022', - lastLockoutDate: '10/26/2022', - lastPasswordChangeDate: '10/20/2022', - updateDate: '3/20/2022', - createDate: '12/22/2021', - failedLoginAttempts: 57, - userGroups: ['b847398a-6875-4d7a-9f6d-231256b81471', '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949'], - }, - { - id: 'd9cbd4cd-6950-42b2-be57-1f5829c6dd19', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Roxanne Pitkins', - email: 'rpitkins5@huffingtonpost.com', - language: 'Bengali', - status: 'invited', - lastLoginDate: '1/2/2022', - lastLockoutDate: '6/4/2022', - lastPasswordChangeDate: '4/28/2022', - updateDate: '1/9/2022', - createDate: '3/21/2022', - failedLoginAttempts: 336, - userGroups: ['397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', 'b847398a-6875-4d7a-9f6d-231256b81471'], - }, - { - id: '515b2c5c-c195-43f2-8e52-4733572030c7', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: "Ag O'Crowley", - email: 'aocrowley6@apache.org', - language: 'Moldovan', - status: 'enabled', - lastLoginDate: '10/19/2022', - lastLockoutDate: '12/4/2021', - lastPasswordChangeDate: '10/5/2022', - updateDate: '3/25/2022', - createDate: '4/23/2022', - failedLoginAttempts: 197, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: 'db8a0800-28b3-4f0b-9152-37debea6b8d7', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Roddy Tebbutt', - email: 'rtebbutt7@upenn.edu', - language: 'Tok Pisin', - status: 'enabled', - lastLoginDate: '9/29/2022', - lastLockoutDate: '11/5/2021', - lastPasswordChangeDate: '9/14/2022', - updateDate: '4/1/2022', - createDate: '8/11/2022', - failedLoginAttempts: 764, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: '3fe38c9b-b5a3-4897-8507-3f062a25659e', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Sharyl Galland', - email: 'sgalland8@cdc.gov', - language: 'Catalan', - status: 'inactive', - lastLoginDate: '10/23/2022', - lastLockoutDate: '11/20/2021', - lastPasswordChangeDate: '6/14/2022', - updateDate: '6/18/2022', - createDate: '3/8/2022', - failedLoginAttempts: 116, - userGroups: ['c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949'], - }, - { - id: '09e99152-bc3e-449f-9fa1-322ab3390b7d', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'My Ringrose', - email: 'mringrose9@blinklist.com', - language: 'Armenian', - status: 'enabled', - lastLoginDate: '3/31/2022', - lastLockoutDate: '8/1/2022', - lastPasswordChangeDate: '2/27/2022', - updateDate: '2/8/2022', - createDate: '8/9/2022', - failedLoginAttempts: 908, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: '5680bd61-9b58-4ecb-ae06-bdfacebe05f2', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Bart Lumm', - email: 'blumma@wordpress.com', - language: 'Māori', - status: 'disabled', - lastLoginDate: '1/15/2022', - lastLockoutDate: '3/30/2022', - lastPasswordChangeDate: '8/7/2022', - updateDate: '6/26/2022', - createDate: '10/22/2022', - failedLoginAttempts: 564, - userGroups: ['397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1'], - }, - { - id: '1c2cb6b5-1b96-47c0-a2b7-f5dd6bd3d325', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Roch Baldam', - email: 'rbaldamb@eventbrite.com', - language: 'Kyrgyz', - status: 'enabled', - lastLoginDate: '3/28/2022', - lastLockoutDate: '3/16/2022', - lastPasswordChangeDate: '12/15/2021', - updateDate: '4/18/2022', - createDate: '8/11/2022', - failedLoginAttempts: 120, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: 'ac6cc4e4-ab38-4920-8646-63c7652fc97a', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Franni Devonport', - email: 'fdevonportc@tinyurl.com', - language: 'Burmese', - status: 'inactive', - lastLoginDate: '8/15/2022', - lastLockoutDate: '3/31/2022', - lastPasswordChangeDate: '2/17/2022', - updateDate: '7/10/2022', - createDate: '3/16/2022', - failedLoginAttempts: 52, - userGroups: ['b847398a-6875-4d7a-9f6d-231256b81471', '2668f09b-320c-48a7-a78a-95047026ec0e'], - }, - { - id: 'a5e5bbe4-acb4-4c40-b15a-eab510338620', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Karalee Guillet', - email: 'kguilletd@de.vu', - language: 'Kurdish', - status: 'invited', - lastLoginDate: '5/14/2022', - lastLockoutDate: '7/6/2022', - lastPasswordChangeDate: '1/26/2022', - updateDate: '12/1/2021', - createDate: '10/19/2022', - failedLoginAttempts: 988, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: 'c775af23-4aec-4d24-a2d1-5b0d666c9eb4', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Lynnet Massimo', - email: 'lmassimoe@fotki.com', - language: 'Swati', - status: 'disabled', - lastLoginDate: '6/8/2022', - lastLockoutDate: '11/4/2021', - lastPasswordChangeDate: '11/25/2021', - updateDate: '5/10/2022', - createDate: '1/21/2022', - failedLoginAttempts: 148, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: '3333e2dc-b8a6-4db3-af00-ac1c1e1d5d9c', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Saraann Boullin', - email: 'sboullinf@reverbnation.com', - language: 'Māori', - status: 'inactive', - lastLoginDate: '12/19/2021', - lastLockoutDate: '10/2/2022', - lastPasswordChangeDate: '1/6/2022', - updateDate: '2/16/2022', - createDate: '12/19/2021', - failedLoginAttempts: 132, - userGroups: ['c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949'], - }, - { - id: '506126e3-2b96-4746-bd0a-1e6b2283021f', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Darrelle Applebee', - email: 'dapplebeeg@indiatimes.com', - language: 'Macedonian', - status: 'inactive', - lastLoginDate: '6/30/2022', - lastLockoutDate: '2/1/2022', - lastPasswordChangeDate: '6/17/2022', - updateDate: '10/22/2022', - createDate: '3/1/2022', - failedLoginAttempts: 229, - userGroups: ['9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', 'b847398a-6875-4d7a-9f6d-231256b81471'], - }, - { - id: 'caf10593-3710-4417-af3d-7015f88f5fe3', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Arline Currom', - email: 'acurromh@aboutads.info', - language: 'Punjabi', - status: 'enabled', - lastLoginDate: '1/18/2022', - lastLockoutDate: '6/29/2022', - lastPasswordChangeDate: '11/27/2021', - updateDate: '5/30/2022', - createDate: '4/7/2022', - failedLoginAttempts: 419, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: 'e2492eeb-bcc2-4c95-8893-27c45c895c9c', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Job Russell', - email: 'jrusselli@networksolutions.com', - language: 'Mongolian', - status: 'inactive', - lastLoginDate: '9/8/2022', - lastLockoutDate: '3/29/2022', - lastPasswordChangeDate: '5/6/2022', - updateDate: '9/23/2022', - createDate: '9/7/2022', - failedLoginAttempts: 924, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: '8c93b359-a719-4453-991c-e2d5bcc965c3', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Helenka Hawkeridge', - email: 'hhawkeridgej@elegantthemes.com', - language: 'Norwegian', - status: 'disabled', - lastLoginDate: '1/24/2022', - lastLockoutDate: '4/6/2022', - lastPasswordChangeDate: '7/20/2022', - updateDate: '5/25/2022', - createDate: '11/15/2021', - failedLoginAttempts: 7, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: '4cd22c7c-baeb-463f-89e5-e0a6bb3ea27e', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Cherida Navarijo', - email: 'cnavarijok@webeden.co.uk', - language: 'Swati', - status: 'inactive', - lastLoginDate: '4/19/2022', - lastLockoutDate: '10/16/2022', - lastPasswordChangeDate: '9/12/2022', - updateDate: '6/30/2022', - createDate: '12/12/2021', - failedLoginAttempts: 178, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: 'ccd03e29-e924-4240-a1e4-b0114c66aae9', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Monte Lissandrini', - email: 'mlissandrinil@dailymail.co.uk', - language: 'English', - status: 'enabled', - lastLoginDate: '1/16/2022', - lastLockoutDate: '10/4/2022', - lastPasswordChangeDate: '4/3/2022', - updateDate: '5/10/2022', - createDate: '7/8/2022', - failedLoginAttempts: 937, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: '7ffa97ca-0702-4bcf-8e32-751bae9aa156', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Cristobal Hincks', - email: 'chincksm@cam.ac.uk', - language: 'Swahili', - status: 'disabled', - lastLoginDate: '1/10/2022', - lastLockoutDate: '11/15/2021', - lastPasswordChangeDate: '7/3/2022', - updateDate: '2/5/2022', - createDate: '11/12/2021', - failedLoginAttempts: 737, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: 'e139ec4b-b49a-48c0-a1a3-1c2e8e95366d', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Darrick Simeoli', - email: 'dsimeolin@craigslist.org', - language: 'Assamese', - status: 'enabled', - lastLoginDate: '6/18/2022', - lastLockoutDate: '6/11/2022', - lastPasswordChangeDate: '1/21/2022', - updateDate: '9/13/2022', - createDate: '12/11/2021', - failedLoginAttempts: 956, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: '30d29d56-cbb2-41fd-9154-94b0f0d9a385', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Vernice Lilford', - email: 'vlilfordo@sohu.com', - language: 'Irish Gaelic', - status: 'invited', - lastLoginDate: '12/2/2021', - lastLockoutDate: '11/27/2021', - lastPasswordChangeDate: '9/3/2022', - updateDate: '1/23/2022', - createDate: '6/17/2022', - failedLoginAttempts: 663, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: '3ecac483-c4df-4971-a357-a0be03c520ca', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Susannah Sans', - email: 'ssansp@linkedin.com', - language: 'Malayalam', - status: 'disabled', - lastLoginDate: '1/13/2022', - lastLockoutDate: '6/20/2022', - lastPasswordChangeDate: '11/2/2021', - updateDate: '7/8/2022', - createDate: '5/7/2022', - failedLoginAttempts: 662, - userGroups: ['c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', '2668f09b-320c-48a7-a78a-95047026ec0e'], - }, - { - id: '2dae8bf8-5fdd-4efa-a493-cbec11b179e2', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Richardo Redhole', - email: 'rredholeq@reference.com', - language: 'Pashto', - status: 'inactive', - lastLoginDate: '2/7/2022', - lastLockoutDate: '7/22/2022', - lastPasswordChangeDate: '6/10/2022', - updateDate: '2/5/2022', - createDate: '3/10/2022', - failedLoginAttempts: 933, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: '0397dd89-72c5-4d0b-a544-dc1c9c0a932d', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Patrizia Cicero', - email: 'pciceror@example.com', - language: 'Dari', - status: 'enabled', - lastLoginDate: '9/19/2022', - lastLockoutDate: '9/22/2022', - lastPasswordChangeDate: '7/29/2022', - updateDate: '2/26/2022', - createDate: '4/18/2022', - failedLoginAttempts: 750, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: '4f2f64c1-1b9b-4217-80c7-7760962215af', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Rupert Bradberry', - email: 'rbradberrys@wsj.com', - language: 'German', - status: 'enabled', - lastLoginDate: '11/18/2021', - lastLockoutDate: '12/13/2021', - lastPasswordChangeDate: '3/27/2022', - updateDate: '3/16/2022', - createDate: '7/21/2022', - failedLoginAttempts: 735, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: '6cefa1e1-4302-4003-81df-3fa4759245a4', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Clarabelle Colpus', - email: 'ccolpust@google.de', - language: 'Estonian', - status: 'invited', - lastLoginDate: '9/8/2022', - lastLockoutDate: '11/28/2021', - lastPasswordChangeDate: '12/8/2021', - updateDate: '10/20/2022', - createDate: '9/15/2022', - failedLoginAttempts: 602, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: '2ba9ae27-7860-42ea-b628-c5484b64b2c6', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Kathi Grahamslaw', - email: 'kgrahamslawu@wikispaces.com', - language: 'Bosnian', - status: 'disabled', - lastLoginDate: '9/29/2022', - lastLockoutDate: '6/24/2022', - lastPasswordChangeDate: '11/5/2021', - updateDate: '8/7/2022', - createDate: '7/5/2022', - failedLoginAttempts: 867, - userGroups: ['c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', 'b847398a-6875-4d7a-9f6d-231256b81471'], - }, - { - id: 'f4bee7c8-7a94-4937-8e6e-ceb55c9ec8b4', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Athena Vick', - email: 'avickv@netlog.com', - language: 'Kashmiri', - status: 'disabled', - lastLoginDate: '3/24/2022', - lastLockoutDate: '2/1/2022', - lastPasswordChangeDate: '12/10/2021', - updateDate: '9/15/2022', - createDate: '12/13/2021', - failedLoginAttempts: 269, - userGroups: ['c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', 'b847398a-6875-4d7a-9f6d-231256b81471'], - }, - { - id: '3ccfeec3-1c96-4205-ae90-3297702d0d59', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Taddeusz Dysert', - email: 'tdysertw@creativecommons.org', - language: 'Māori', - status: 'invited', - lastLoginDate: '5/4/2022', - lastLockoutDate: '7/15/2022', - lastPasswordChangeDate: '5/24/2022', - updateDate: '8/21/2022', - createDate: '2/22/2022', - failedLoginAttempts: 426, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: 'cb0e3a46-1e99-4dc3-a206-462c3d985916', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Brook Leyden', - email: 'bleydenx@ameblo.jp', - language: 'Kashmiri', - status: 'inactive', - lastLoginDate: '5/31/2022', - lastLockoutDate: '12/2/2021', - lastPasswordChangeDate: '1/18/2022', - updateDate: '12/16/2021', - createDate: '8/11/2022', - failedLoginAttempts: 615, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: 'eb5af046-f2af-490f-a195-b0faff73f538', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Elisabet Meggison', - email: 'emeggisony@issuu.com', - language: 'Burmese', - status: 'inactive', - lastLoginDate: '4/13/2022', - lastLockoutDate: '6/12/2022', - lastPasswordChangeDate: '4/29/2022', - updateDate: '9/23/2022', - createDate: '1/7/2022', - failedLoginAttempts: 624, - userGroups: ['c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2'], - }, - { - id: '42037971-4e06-41a8-be76-04313571fe54', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Rees Mordecai', - email: 'rmordecaiz@unblog.fr', - language: 'Chinese', - status: 'invited', - lastLoginDate: '9/8/2022', - lastLockoutDate: '8/11/2022', - lastPasswordChangeDate: '6/6/2022', - updateDate: '11/23/2021', - createDate: '12/14/2021', - failedLoginAttempts: 740, - userGroups: ['2668f09b-320c-48a7-a78a-95047026ec0e', '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2'], - }, - { - id: 'd71ba775-2920-42de-b5a1-09104e81cf27', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Pamela Sheering', - email: 'psheering10@joomla.org', - language: 'Gujarati', - status: 'inactive', - lastLoginDate: '4/2/2022', - lastLockoutDate: '9/1/2022', - lastPasswordChangeDate: '10/14/2022', - updateDate: '6/28/2022', - createDate: '11/9/2021', - failedLoginAttempts: 410, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: 'ed94e56b-64d5-43d0-9c9c-7149a0b26657', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Raychel Hazell', - email: 'rhazell11@soup.io', - language: 'Japanese', - status: 'inactive', - lastLoginDate: '2/4/2022', - lastLockoutDate: '2/19/2022', - lastPasswordChangeDate: '10/21/2022', - updateDate: '1/20/2022', - createDate: '9/6/2022', - failedLoginAttempts: 463, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: '8acfe50e-2e15-460d-b368-0510d08fea4c', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Martie Keasey', - email: 'mkeasey12@howstuffworks.com', - language: 'Kazakh', - status: 'enabled', - lastLoginDate: '4/1/2022', - lastLockoutDate: '2/10/2022', - lastPasswordChangeDate: '10/9/2022', - updateDate: '8/20/2022', - createDate: '5/18/2022', - failedLoginAttempts: 686, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: '73401c00-3b05-465b-ab59-8def5af4ec54', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Justus Szapiro', - email: 'jszapiro13@wp.com', - language: 'Persian', - status: 'inactive', - lastLoginDate: '5/5/2022', - lastLockoutDate: '1/20/2022', - lastPasswordChangeDate: '1/27/2022', - updateDate: '4/18/2022', - createDate: '1/30/2022', - failedLoginAttempts: 34, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: 'ffed1abb-aee0-45f1-9915-5ea7da165011', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Thea Blannin', - email: 'tblannin14@flavors.me', - language: 'Gujarati', - status: 'enabled', - lastLoginDate: '2/20/2022', - lastLockoutDate: '7/13/2022', - lastPasswordChangeDate: '10/19/2022', - updateDate: '9/4/2022', - createDate: '11/3/2021', - failedLoginAttempts: 3, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: '754fafb2-ec86-4313-8c5a-26a0a460df70', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Reinhold Moseby', - email: 'rmoseby15@mapquest.com', - language: 'Hungarian', - status: 'inactive', - lastLoginDate: '8/2/2022', - lastLockoutDate: '5/22/2022', - lastPasswordChangeDate: '5/25/2022', - updateDate: '8/15/2022', - createDate: '6/5/2022', - failedLoginAttempts: 197, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: 'eae3c035-1b9d-4d1d-b626-89a7c3b3bc39', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Prudi Waulker', - email: 'pwaulker16@who.int', - language: 'Swedish', - status: 'enabled', - lastLoginDate: '12/26/2021', - lastLockoutDate: '8/4/2022', - lastPasswordChangeDate: '12/7/2021', - updateDate: '12/14/2021', - createDate: '1/9/2022', - failedLoginAttempts: 264, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: '4bbf5669-99ec-4e60-b159-2198990ee8f1', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Kile Espinoy', - email: 'kespinoy17@cdc.gov', - language: 'Bosnian', - status: 'enabled', - lastLoginDate: '6/18/2022', - lastLockoutDate: '9/8/2022', - lastPasswordChangeDate: '5/11/2022', - updateDate: '7/12/2022', - createDate: '6/17/2022', - failedLoginAttempts: 367, - userGroups: ['397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949'], - }, - { - id: 'bc17c986-9869-49f4-baee-d888bf013f27', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Tadeas Fitzer', - email: 'tfitzer18@merriam-webster.com', - language: 'German', - status: 'inactive', - lastLoginDate: '12/11/2021', - lastLockoutDate: '7/11/2022', - lastPasswordChangeDate: '9/22/2022', - updateDate: '5/21/2022', - createDate: '9/27/2022', - failedLoginAttempts: 988, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: 'f015b8a7-35b7-4859-a506-253ee95f92f4', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Murielle Roskams', - email: 'mroskams19@simplemachines.org', - language: 'Azeri', - status: 'inactive', - lastLoginDate: '11/13/2021', - lastLockoutDate: '4/8/2022', - lastPasswordChangeDate: '1/6/2022', - updateDate: '9/1/2022', - createDate: '7/25/2022', - failedLoginAttempts: 113, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: 'b5fd8d4f-eecc-4bea-b841-b4ba3621e8ba', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Clarice Mation', - email: 'cmation1a@geocities.jp', - language: 'Icelandic', - status: 'invited', - lastLoginDate: '6/26/2022', - lastLockoutDate: '11/1/2022', - lastPasswordChangeDate: '7/27/2022', - updateDate: '11/17/2021', - createDate: '7/22/2022', - failedLoginAttempts: 543, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: '0bfe956a-5293-48bf-8c43-fd9be5c8dd19', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Sarajane Mc Caghan', - email: 'smc1b@amazon.com', - language: 'Telugu', - status: 'enabled', - lastLoginDate: '3/6/2022', - lastLockoutDate: '11/11/2021', - lastPasswordChangeDate: '5/13/2022', - updateDate: '3/1/2022', - createDate: '5/9/2022', - failedLoginAttempts: 343, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: 'f2914aaa-de0a-4285-b820-88d22ae7a566', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Aldis Instone', - email: 'ainstone1c@topsy.com', - language: 'Macedonian', - status: 'invited', - lastLoginDate: '1/7/2022', - lastLockoutDate: '9/27/2022', - lastPasswordChangeDate: '6/16/2022', - updateDate: '8/21/2022', - createDate: '9/12/2022', - failedLoginAttempts: 345, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: 'f5489ee0-589d-47e5-8c11-b5e2ef027519', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Riley Pfeffle', - email: 'rpfeffle1d@toplist.cz', - language: 'Quechua', - status: 'enabled', - lastLoginDate: '6/27/2022', - lastLockoutDate: '9/23/2022', - lastPasswordChangeDate: '12/22/2021', - updateDate: '8/22/2022', - createDate: '1/2/2022', - failedLoginAttempts: 838, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: '293074af-8188-4151-b025-2b43f6aa6c2c', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Beverly Andreone', - email: 'bandreone1e@pbs.org', - language: 'Bosnian', - status: 'disabled', - lastLoginDate: '2/13/2022', - lastLockoutDate: '7/6/2022', - lastPasswordChangeDate: '4/4/2022', - updateDate: '6/15/2022', - createDate: '4/16/2022', - failedLoginAttempts: 459, - userGroups: ['2668f09b-320c-48a7-a78a-95047026ec0e', '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949'], - }, - { - id: '93765192-b40f-4bf2-8c06-1d5ffb6989ae', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Katheryn Deedes', - email: 'kdeedes1f@theglobeandmail.com', - language: 'Romanian', - status: 'disabled', - lastLoginDate: '4/21/2022', - lastLockoutDate: '9/1/2022', - lastPasswordChangeDate: '1/18/2022', - updateDate: '2/19/2022', - createDate: '2/16/2022', - failedLoginAttempts: 62, - userGroups: ['397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', 'b847398a-6875-4d7a-9f6d-231256b81471'], - }, - { - id: '59f1023c-7ce6-4c78-a1ee-dcb4625b9281', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Carma Soares', - email: 'csoares1g@de.vu', - language: 'Irish Gaelic', - status: 'enabled', - lastLoginDate: '5/28/2022', - lastLockoutDate: '8/14/2022', - lastPasswordChangeDate: '12/13/2021', - updateDate: '11/9/2021', - createDate: '9/5/2022', - failedLoginAttempts: 445, - userGroups: ['c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2'], - }, - { - id: '42592a81-f584-4b77-b312-b8e268203c22', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Merwin Forbear', - email: 'mforbear1h@jalbum.net', - language: 'Japanese', - status: 'disabled', - lastLoginDate: '2/23/2022', - lastLockoutDate: '11/17/2021', - lastPasswordChangeDate: '4/1/2022', - updateDate: '12/6/2021', - createDate: '4/24/2022', - failedLoginAttempts: 30, - userGroups: ['397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', '2668f09b-320c-48a7-a78a-95047026ec0e'], - }, - { - id: '8ad78a84-8183-4833-9f8b-07b3ea8a881c', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Laurene Kimberly', - email: 'lkimberly1i@prnewswire.com', - language: 'Hungarian', - status: 'inactive', - lastLoginDate: '9/19/2022', - lastLockoutDate: '8/31/2022', - lastPasswordChangeDate: '7/15/2022', - updateDate: '8/19/2022', - createDate: '4/26/2022', - failedLoginAttempts: 988, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: '3f7bc8b5-df8b-4a79-a8bf-f379c63b8d01', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Marketa McGillreich', - email: 'mmcgillreich1j@alexa.com', - language: 'Quechua', - status: 'invited', - lastLoginDate: '2/19/2022', - lastLockoutDate: '9/5/2022', - lastPasswordChangeDate: '2/22/2022', - updateDate: '11/27/2021', - createDate: '6/1/2022', - failedLoginAttempts: 722, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: '09901602-688a-4c83-a977-51c16950a2c1', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Katha Caddell', - email: 'kcaddell1k@domainmarket.com', - language: 'Filipino', - status: 'enabled', - lastLoginDate: '4/3/2022', - lastLockoutDate: '5/5/2022', - lastPasswordChangeDate: '2/20/2022', - updateDate: '10/30/2022', - createDate: '9/18/2022', - failedLoginAttempts: 65, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: '060972c7-9b23-4788-8dc3-c2fcec1d002e', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Alonzo Gallatly', - email: 'agallatly1l@posterous.com', - language: 'Polish', - status: 'enabled', - lastLoginDate: '5/4/2022', - lastLockoutDate: '11/13/2021', - lastPasswordChangeDate: '7/28/2022', - updateDate: '11/9/2021', - createDate: '7/13/2022', - failedLoginAttempts: 326, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: 'ccf7639c-09d9-4de6-88ec-be51be7f9d69', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Sabine Chimenti', - email: 'schimenti1m@ow.ly', - language: 'Spanish', - status: 'enabled', - lastLoginDate: '12/25/2021', - lastLockoutDate: '8/26/2022', - lastPasswordChangeDate: '9/6/2022', - updateDate: '9/9/2022', - createDate: '5/16/2022', - failedLoginAttempts: 468, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: '2c6b24a4-c0d2-4efe-8a09-68b61d2a17ef', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Stephanie Schanke', - email: 'sschanke1n@epa.gov', - language: 'Indonesian', - status: 'disabled', - lastLoginDate: '2/13/2022', - lastLockoutDate: '2/27/2022', - lastPasswordChangeDate: '7/14/2022', - updateDate: '4/14/2022', - createDate: '5/19/2022', - failedLoginAttempts: 177, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: 'ff7a2003-f8d7-4fbc-96cb-ee221bdc01c9', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Lonee Andrusov', - email: 'landrusov1o@pagesperso-orange.fr', - language: 'Haitian Creole', - status: 'inactive', - lastLoginDate: '11/2/2021', - lastLockoutDate: '6/8/2022', - lastPasswordChangeDate: '9/25/2022', - updateDate: '12/14/2021', - createDate: '4/1/2022', - failedLoginAttempts: 485, - userGroups: ['2668f09b-320c-48a7-a78a-95047026ec0e', 'b847398a-6875-4d7a-9f6d-231256b81471'], - }, - { - id: 'b7f7b275-f62d-44ba-a6b0-0e7e83fe4e49', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Darin Rahlof', - email: 'drahlof1p@diigo.com', - language: 'Luxembourgish', - status: 'invited', - lastLoginDate: '5/3/2022', - lastLockoutDate: '5/12/2022', - lastPasswordChangeDate: '1/2/2022', - updateDate: '3/4/2022', - createDate: '5/24/2022', - failedLoginAttempts: 254, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: '18a9a6bc-ae4c-49b0-8afd-43bc214053f4', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Biddy Mellmoth', - email: 'bmellmoth1q@youtu.be', - language: 'Kashmiri', - status: 'disabled', - lastLoginDate: '1/21/2022', - lastLockoutDate: '8/18/2022', - lastPasswordChangeDate: '3/10/2022', - updateDate: '1/18/2022', - createDate: '12/3/2021', - failedLoginAttempts: 892, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: '14fa1c36-1252-433e-a1ad-63cf5c9aba62', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Carissa Wallsam', - email: 'cwallsam1r@t.co', - language: 'Maltese', - status: 'invited', - lastLoginDate: '12/20/2021', - lastLockoutDate: '8/24/2022', - lastPasswordChangeDate: '4/25/2022', - updateDate: '5/2/2022', - createDate: '5/22/2022', - failedLoginAttempts: 474, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: '7cb9327f-6aeb-47af-80b3-4b85abde1f5f', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Jandy Gilffilland', - email: 'jgilffilland1s@usda.gov', - language: 'Telugu', - status: 'invited', - lastLoginDate: '3/7/2022', - lastLockoutDate: '6/30/2022', - lastPasswordChangeDate: '8/30/2022', - updateDate: '6/2/2022', - createDate: '3/14/2022', - failedLoginAttempts: 991, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: '0574e903-ba72-49ee-b838-4eb200e68612', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Cyndy Ragbourne', - email: 'cragbourne1t@clickbank.net', - language: 'Punjabi', - status: 'inactive', - lastLoginDate: '7/7/2022', - lastLockoutDate: '8/20/2022', - lastPasswordChangeDate: '10/23/2022', - updateDate: '2/22/2022', - createDate: '12/2/2021', - failedLoginAttempts: 78, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: '6e4515d6-4a67-4f47-8783-1d074c1d1f75', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Isidro Dysart', - email: 'idysart1u@reverbnation.com', - language: 'Ndebele', - status: 'disabled', - lastLoginDate: '8/10/2022', - lastLockoutDate: '11/23/2021', - lastPasswordChangeDate: '8/13/2022', - updateDate: '11/9/2021', - createDate: '10/18/2022', - failedLoginAttempts: 260, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: '4faa2064-6776-4cc0-8bc1-3ceaa5884a0f', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Aldin Milmore', - email: 'amilmore1v@soundcloud.com', - language: 'Swahili', - status: 'enabled', - lastLoginDate: '6/22/2022', - lastLockoutDate: '2/14/2022', - lastPasswordChangeDate: '5/16/2022', - updateDate: '4/21/2022', - createDate: '9/25/2022', - failedLoginAttempts: 722, - userGroups: ['b847398a-6875-4d7a-9f6d-231256b81471', '2668f09b-320c-48a7-a78a-95047026ec0e'], - }, - { - id: '0cccec0d-d7c4-47bc-97b0-bd55dca42dd7', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Alex Schanke', - email: 'aschanke1w@friendfeed.com', - language: 'Latvian', - status: 'invited', - lastLoginDate: '10/26/2022', - lastLockoutDate: '1/22/2022', - lastPasswordChangeDate: '7/29/2022', - updateDate: '2/12/2022', - createDate: '4/2/2022', - failedLoginAttempts: 309, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: 'd6ffe266-f024-45c2-a1cd-f39bdbc00a5b', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Aundrea Vassie', - email: 'avassie1x@dagondesign.com', - language: 'Armenian', - status: 'disabled', - lastLoginDate: '10/9/2022', - lastLockoutDate: '10/3/2022', - lastPasswordChangeDate: '10/5/2022', - updateDate: '1/10/2022', - createDate: '4/17/2022', - failedLoginAttempts: 552, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: '72d3047a-8c48-4425-ad11-a4a64281e7e5', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Minna Kitley', - email: 'mkitley1y@dmoz.org', - language: 'Kannada', - status: 'inactive', - lastLoginDate: '7/26/2022', - lastLockoutDate: '4/7/2022', - lastPasswordChangeDate: '7/5/2022', - updateDate: '9/24/2022', - createDate: '6/10/2022', - failedLoginAttempts: 37, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: 'ebfe0335-6926-433d-90de-82f9661955f9', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Jada Josskowitz', - email: 'jjosskowitz1z@com.com', - language: 'Hungarian', - status: 'enabled', - lastLoginDate: '2/16/2022', - lastLockoutDate: '12/29/2021', - lastPasswordChangeDate: '12/29/2021', - updateDate: '5/31/2022', - createDate: '7/29/2022', - failedLoginAttempts: 927, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: 'e6e833fb-a872-4364-893c-b6bfc9802043', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Gail Blaxall', - email: 'gblaxall20@arizona.edu', - language: 'Malagasy', - status: 'disabled', - lastLoginDate: '1/15/2022', - lastLockoutDate: '7/28/2022', - lastPasswordChangeDate: '1/4/2022', - updateDate: '3/23/2022', - createDate: '12/25/2021', - failedLoginAttempts: 508, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: 'abd4d295-9ebb-4a0d-bc07-e2a662671e18', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Alethea Feldmann', - email: 'afeldmann21@nba.com', - language: 'Hebrew', - status: 'disabled', - lastLoginDate: '2/15/2022', - lastLockoutDate: '5/12/2022', - lastPasswordChangeDate: '10/28/2022', - updateDate: '5/11/2022', - createDate: '7/16/2022', - failedLoginAttempts: 527, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: '29d42fe1-b465-4dfe-8d29-10286474d625', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Donna Woodley', - email: 'dwoodley22@tuttocitta.it', - language: 'Thai', - status: 'invited', - lastLoginDate: '8/1/2022', - lastLockoutDate: '4/15/2022', - lastPasswordChangeDate: '7/30/2022', - updateDate: '12/19/2021', - createDate: '4/29/2022', - failedLoginAttempts: 61, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: '690535f5-194a-4e32-b569-608fbf279059', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Feodora Blazic', - email: 'fblazic23@abc.net.au', - language: 'Kyrgyz', - status: 'enabled', - lastLoginDate: '2/18/2022', - lastLockoutDate: '11/25/2021', - lastPasswordChangeDate: '9/22/2022', - updateDate: '6/7/2022', - createDate: '8/31/2022', - failedLoginAttempts: 368, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'b847398a-6875-4d7a-9f6d-231256b81471', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: '13b5e542-e779-48df-b7f9-2c7def0e2e55', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Barris Dougary', - email: 'bdougary24@multiply.com', - language: 'Lithuanian', - status: 'disabled', - lastLoginDate: '3/1/2022', - lastLockoutDate: '10/1/2022', - lastPasswordChangeDate: '10/5/2022', - updateDate: '7/1/2022', - createDate: '5/25/2022', - failedLoginAttempts: 982, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: 'af5bfaaf-1597-4ad1-aa1e-206f78a91674', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Bennie Gillbanks', - email: 'bgillbanks25@aboutads.info', - language: 'Swedish', - status: 'inactive', - lastLoginDate: '8/3/2022', - lastLockoutDate: '3/18/2022', - lastPasswordChangeDate: '4/29/2022', - updateDate: '1/4/2022', - createDate: '1/12/2022', - failedLoginAttempts: 868, - userGroups: ['397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949'], - }, - { - id: '8f39464a-2e49-498b-b8b8-cf2926978e83', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Micaela Davidge', - email: 'mdavidge26@fema.gov', - language: 'Dhivehi', - status: 'inactive', - lastLoginDate: '8/6/2022', - lastLockoutDate: '1/15/2022', - lastPasswordChangeDate: '7/14/2022', - updateDate: '5/21/2022', - createDate: '3/28/2022', - failedLoginAttempts: 931, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: 'ac4785f1-b6f5-4d2d-99e6-f62a96947f8c', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Cassius Vannet', - email: 'cvannet27@wufoo.com', - language: 'Icelandic', - status: 'inactive', - lastLoginDate: '2/22/2022', - lastLockoutDate: '9/28/2022', - lastPasswordChangeDate: '5/24/2022', - updateDate: '7/26/2022', - createDate: '1/30/2022', - failedLoginAttempts: 739, - userGroups: ['2668f09b-320c-48a7-a78a-95047026ec0e', '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949'], - }, - { - id: 'd89ada67-eca1-475a-9273-0c69b56116da', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Rozanne Wren', - email: 'rwren28@flavors.me', - language: 'Filipino', - status: 'disabled', - lastLoginDate: '5/6/2022', - lastLockoutDate: '5/17/2022', - lastPasswordChangeDate: '11/12/2021', - updateDate: '4/9/2022', - createDate: '6/16/2022', - failedLoginAttempts: 682, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: '03ced59c-40b3-482a-8470-b12f56974a0f', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Matthiew McClinton', - email: 'mmcclinton29@diigo.com', - language: 'Croatian', - status: 'invited', - lastLoginDate: '11/14/2021', - lastLockoutDate: '1/11/2022', - lastPasswordChangeDate: '6/19/2022', - updateDate: '7/4/2022', - createDate: '9/21/2022', - failedLoginAttempts: 975, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: 'dcfba56e-5f3c-446b-a4ef-18da66df8181', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Jorry McTear', - email: 'jmctear2a@nytimes.com', - language: 'German', - status: 'inactive', - lastLoginDate: '1/10/2022', - lastLockoutDate: '4/17/2022', - lastPasswordChangeDate: '9/28/2022', - updateDate: '1/21/2022', - createDate: '6/30/2022', - failedLoginAttempts: 357, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: '63be3c07-9178-4c2e-810a-6bbdbbe16dcb', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Beale Filipychev', - email: 'bfilipychev2b@free.fr', - language: 'Tetum', - status: 'disabled', - lastLoginDate: '10/25/2022', - lastLockoutDate: '1/5/2022', - lastPasswordChangeDate: '6/4/2022', - updateDate: '7/17/2022', - createDate: '8/22/2022', - failedLoginAttempts: 482, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: 'c57661ef-8f66-4502-91cb-2069a186ce79', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Gabriel Gun', - email: 'ggun2c@nature.com', - language: 'Albanian', - status: 'inactive', - lastLoginDate: '10/5/2022', - lastLockoutDate: '3/27/2022', - lastPasswordChangeDate: '5/21/2022', - updateDate: '10/17/2022', - createDate: '12/14/2021', - failedLoginAttempts: 958, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: 'b5fbd289-2900-4328-8d1f-0a6780be3585', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Horten Wenban', - email: 'hwenban2d@sciencedirect.com', - language: 'Italian', - status: 'disabled', - lastLoginDate: '2/25/2022', - lastLockoutDate: '7/6/2022', - lastPasswordChangeDate: '10/30/2022', - updateDate: '7/31/2022', - createDate: '12/16/2021', - failedLoginAttempts: 698, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: '9ca26535-5288-4b37-8e3b-96b3530de3d1', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Englebert Littrick', - email: 'elittrick2e@t.co', - language: 'Bulgarian', - status: 'enabled', - lastLoginDate: '6/5/2022', - lastLockoutDate: '9/16/2022', - lastPasswordChangeDate: '7/22/2022', - updateDate: '12/2/2021', - createDate: '9/16/2022', - failedLoginAttempts: 340, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: '19998fc5-2902-4646-9cf2-395404da2b2a', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Maxy Toffel', - email: 'mtoffel2f@fda.gov', - language: 'Tswana', - status: 'inactive', - lastLoginDate: '7/1/2022', - lastLockoutDate: '2/15/2022', - lastPasswordChangeDate: '9/9/2022', - updateDate: '8/28/2022', - createDate: '2/5/2022', - failedLoginAttempts: 264, - userGroups: [ - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'b847398a-6875-4d7a-9f6d-231256b81471', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: '468a904c-f87c-4079-920b-74c6aa3a5242', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Thomasina Concannon', - email: 'tconcannon2g@cloudflare.com', - language: 'Tajik', - status: 'inactive', - lastLoginDate: '11/2/2021', - lastLockoutDate: '3/27/2022', - lastPasswordChangeDate: '12/17/2021', - updateDate: '4/30/2022', - createDate: '10/28/2022', - failedLoginAttempts: 985, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: '60336828-85ed-4b5a-abf6-869d6902ad93', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Jany Manginot', - email: 'jmanginot2h@chron.com', - language: 'Quechua', - status: 'disabled', - lastLoginDate: '6/6/2022', - lastLockoutDate: '2/2/2022', - lastPasswordChangeDate: '2/7/2022', - updateDate: '10/5/2022', - createDate: '4/18/2022', - failedLoginAttempts: 307, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - 'b847398a-6875-4d7a-9f6d-231256b81471', - ], - }, - { - id: '2b0c8ffe-4bcb-4df6-b519-3358a5c90480', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Agace Daughtrey', - email: 'adaughtrey2i@nifty.com', - language: 'Moldovan', - status: 'inactive', - lastLoginDate: '9/14/2022', - lastLockoutDate: '1/9/2022', - lastPasswordChangeDate: '8/25/2022', - updateDate: '12/29/2021', - createDate: '6/26/2022', - failedLoginAttempts: 599, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - ], - }, - { - id: 'c967ff49-f967-449b-9aab-e5ca72255a61', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Debor Maillard', - email: 'dmaillard2j@jigsy.com', - language: 'Norwegian', - status: 'enabled', - lastLoginDate: '10/21/2022', - lastLockoutDate: '8/19/2022', - lastPasswordChangeDate: '2/14/2022', - updateDate: '5/11/2022', - createDate: '12/18/2021', - failedLoginAttempts: 294, - userGroups: [ - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - 'b847398a-6875-4d7a-9f6d-231256b81471', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: '594040ce-c6f7-49e7-8f4a-ffa8c42a2102', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Lurette Armiger', - email: 'larmiger2k@google.pl', - language: 'Greek', - status: 'enabled', - lastLoginDate: '7/13/2022', - lastLockoutDate: '2/28/2022', - lastPasswordChangeDate: '11/7/2021', - updateDate: '6/4/2022', - createDate: '7/19/2022', - failedLoginAttempts: 881, - userGroups: ['b847398a-6875-4d7a-9f6d-231256b81471', '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949'], - }, - { - id: '33806fc1-d4a9-4ddb-8d57-3c087ea1f489', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Melvin Colby', - email: 'mcolby2l@abc.net.au', - language: 'French', - status: 'invited', - lastLoginDate: '11/18/2021', - lastLockoutDate: '11/28/2021', - lastPasswordChangeDate: '10/21/2022', - updateDate: '5/1/2022', - createDate: '5/2/2022', - failedLoginAttempts: 903, - userGroups: [ - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: 'a59a5259-9393-4c49-a5be-56a475b27640', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Junia Hordle', - email: 'jhordle2m@opera.com', - language: 'Albanian', - status: 'enabled', - lastLoginDate: '7/11/2022', - lastLockoutDate: '5/6/2022', - lastPasswordChangeDate: '1/21/2022', - updateDate: '3/7/2022', - createDate: '1/30/2022', - failedLoginAttempts: 482, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - '2668f09b-320c-48a7-a78a-95047026ec0e', - ], - }, - { - id: '9e96841c-131d-420a-bdc8-4ba087f0f11c', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Daveen Seccombe', - email: 'dseccombe2n@blogtalkradio.com', - language: 'Estonian', - status: 'disabled', - lastLoginDate: '1/29/2022', - lastLockoutDate: '11/4/2021', - lastPasswordChangeDate: '2/13/2022', - updateDate: '8/15/2022', - createDate: '8/26/2022', - failedLoginAttempts: 538, - userGroups: [ - 'b847398a-6875-4d7a-9f6d-231256b81471', - '2668f09b-320c-48a7-a78a-95047026ec0e', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - ], - }, - { - id: '84eaae2a-9aa8-4745-b4c3-fad2f5632e85', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Adolphus MacCombe', - email: 'amaccombe2o@xing.com', - language: 'Japanese', - status: 'inactive', - lastLoginDate: '4/5/2022', - lastLockoutDate: '11/14/2021', - lastPasswordChangeDate: '8/10/2022', - updateDate: '3/11/2022', - createDate: '2/27/2022', - failedLoginAttempts: 314, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '9a9ad4e9-3b5b-4fe7-b0d9-e301b9675949', - 'b847398a-6875-4d7a-9f6d-231256b81471', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], - }, - { - id: '6fe86358-f3fd-4f7d-8d28-e867562569f2', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Abagael Rivard', - email: 'arivard2p@cnbc.com', - language: 'Somali', - status: 'disabled', - lastLoginDate: '9/12/2022', - lastLockoutDate: '11/28/2021', - lastPasswordChangeDate: '4/3/2022', - updateDate: '8/21/2022', - createDate: '7/31/2022', - failedLoginAttempts: 58, - userGroups: ['c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', 'b847398a-6875-4d7a-9f6d-231256b81471'], - }, - { - id: '365fd725-81c7-48f0-be68-4dbcf15f1ca9', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Angeli Landre', - email: 'alandre2q@telegraph.co.uk', - language: 'Persian', - status: 'disabled', - lastLoginDate: '10/22/2022', - lastLockoutDate: '7/14/2022', - lastPasswordChangeDate: '3/13/2022', - updateDate: '8/15/2022', - createDate: '3/14/2022', - failedLoginAttempts: 788, - userGroups: ['2668f09b-320c-48a7-a78a-95047026ec0e', 'b847398a-6875-4d7a-9f6d-231256b81471'], - }, - { - id: 'b6028623-995e-4eee-8142-723141030692', - type: 'user', - hasChildren: false, - parentId: '', - contentStartNodes: [], - mediaStartNodes: [], - icon: 'umb:user', - name: 'Brett Dommett', - email: 'bdommett2r@wordpress.org', - language: 'Fijian', - status: 'inactive', - lastLoginDate: '8/31/2022', - lastLockoutDate: '10/7/2022', - lastPasswordChangeDate: '2/16/2022', - updateDate: '12/26/2021', - createDate: '2/21/2022', - failedLoginAttempts: 265, - userGroups: [ - '397f3a8b-4ca3-4b01-9dd3-94e5c9eaa9b2', - '2668f09b-320c-48a7-a78a-95047026ec0e', - 'c630d49e-4e7b-42ea-b2bc-edc0edacb6b1', - ], + id: '82e11d3d-b91d-43c9-9071-34d28e62e81d', + type: 'user', + $type: 'UserResponseModel', + contentStartNodeIds: [], + mediaStartNodeIds: [], + name: 'Amelie Walker', + email: 'awalker1@domain.com', + languageIsoCode: 'Japanese', + state: UserStateModel.INACTIVE, + lastLoginDate: '4/12/2023', + lastlockoutDate: '', + lastPasswordChangeDate: '4/1/2023', + updateDate: '4/12/2023', + createDate: '4/12/2023', + failedLoginAttempts: 0, + }, + { + id: 'aa1d83a9-bc7f-47d2-b288-58d8a31f5017', + type: 'user', + $type: 'UserResponseModel', + contentStartNodeIds: [], + mediaStartNodeIds: [], + name: 'Oliver Kim', + email: 'okim1@domain.com', + languageIsoCode: 'Russian', + state: UserStateModel.ACTIVE, + lastLoginDate: '4/11/2023', + lastlockoutDate: '', + lastPasswordChangeDate: '4/5/2023', + updateDate: '4/11/2023', + createDate: '4/11/2023', + failedLoginAttempts: 0, + }, + { + id: 'ff2f4a50-d3d4-4bc4-869d-c7948c160e54', + type: 'user', + $type: 'UserResponseModel', + contentStartNodeIds: [], + mediaStartNodeIds: [], + name: 'Eliana Nieves', + email: 'enieves1@domain.com', + languageIsoCode: 'Spanish', + state: UserStateModel.INVITED, + lastLoginDate: '4/10/2023', + lastlockoutDate: '', + lastPasswordChangeDate: '4/6/2023', + updateDate: '4/10/2023', + createDate: '4/10/2023', + failedLoginAttempts: 0, + }, + { + id: 'c290c6d9-9f12-4838-8567-621b52a178de', + type: 'user', + $type: 'UserResponseModel', + contentStartNodeIds: [], + mediaStartNodeIds: [], + name: 'Jasmine Patel', + email: 'jpatel1@domain.com', + languageIsoCode: 'Hindi', + state: UserStateModel.DISABLED, + lastLoginDate: '4/9/2023', + lastlockoutDate: '', + lastPasswordChangeDate: '4/7/2023', + updateDate: '4/9/2023', + createDate: '4/9/2023', + failedLoginAttempts: 0, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/users.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/users.handlers.ts index e56784f793..e6fab8906a 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/users.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/users.handlers.ts @@ -1,111 +1,116 @@ import { rest } from 'msw'; import { v4 as uuidv4 } from 'uuid'; + import { umbUsersData } from '../data/users.data'; -import type { UserDetails } from '@umbraco-cms/backoffice/models'; +import { umbracoPath } from '@umbraco-cms/backoffice/utils'; + +const slug = '/users'; -// TODO: add schema export const handlers = [ - rest.get('/umbraco/backoffice/users/list/items', (req, res, ctx) => { - const items = umbUsersData.getAll(); - - const response = { - total: items.length, - items, - }; + rest.get(umbracoPath(`${slug}`), (req, res, ctx) => { + const response = umbUsersData.getAll(); return res(ctx.status(200), ctx.json(response)); }), - rest.get('/umbraco/backoffice/users/details/:id', (req, res, ctx) => { + rest.get(umbracoPath(`${slug}/:id`), (req, res, ctx) => { const id = req.params.id as string; if (!id) return; - const user = umbUsersData.getById(id); return res(ctx.status(200), ctx.json(user)); }), - rest.get('/umbraco/backoffice/users/getByKeys', (req, res, ctx) => { - const ids = req.url.searchParams.getAll('id'); - if (ids.length === 0) return; - const users = umbUsersData.getByIds(ids); - - return res(ctx.status(200), ctx.json(users)); - }), - - rest.post('/umbraco/backoffice/users/save', async (req, res, ctx) => { + rest.put(umbracoPath(`${slug}/:id`), async (req, res, ctx) => { const data = await req.json(); if (!data) return; const saved = umbUsersData.save(data); - console.log('saved', saved); - return res(ctx.status(200), ctx.json(saved)); }), - rest.post('/umbraco/backoffice/users/invite', async (req, res, ctx) => { - const data = await req.json(); - if (!data) return; + // rest.get('/umbraco/backoffice/users/getByKeys', (req, res, ctx) => { + // const ids = req.url.searchParams.getAll('id'); + // if (ids.length === 0) return; + // const users = umbUsersData.getByIds(ids); - const newUser: UserDetails = { - id: uuidv4(), - name: data.name, - email: data.email, - status: 'invited', - language: 'en', - updateDate: new Date().toISOString(), - createDate: new Date().toISOString(), - failedLoginAttempts: 0, - parentId: '', - hasChildren: false, - type: 'user', - icon: 'umb:icon-user', - userGroups: data.userGroups, - contentStartNodes: [], - mediaStartNodes: [], - }; + // return res(ctx.status(200), ctx.json(users)); + // }), - const invited = umbUsersData.save(newUser); + // rest.post('/umbraco/backoffice/users/save', async (req, res, ctx) => { + // const data = await req.json(); + // if (!data) return; - console.log('invited', invited); + // const saved = umbUsersData.save(data); - return res(ctx.status(200), ctx.json(invited)); - }), + // console.log('saved', saved); - rest.post>('/umbraco/backoffice/users/enable', async (req, res, ctx) => { - const data = await req.json(); - if (!data) return; + // return res(ctx.status(200), ctx.json(saved)); + // }), - const enabledKeys = umbUsersData.enable(data); + // rest.post('/umbraco/backoffice/users/invite', async (req, res, ctx) => { + // const data = await req.json(); + // if (!data) return; - return res(ctx.status(200), ctx.json(enabledKeys)); - }), + // const newUser: UserDetails = { + // id: uuidv4(), + // name: data.name, + // email: data.email, + // status: 'invited', + // language: 'en', + // updateDate: new Date().toISOString(), + // createDate: new Date().toISOString(), + // failedLoginAttempts: 0, + // parentId: '', + // hasChildren: false, + // type: 'user', + // icon: 'umb:icon-user', + // userGroups: data.userGroups, + // contentStartNodes: [], + // mediaStartNodes: [], + // }; - rest.post>('/umbraco/backoffice/users/disable', async (req, res, ctx) => { - const data = await req.json(); - if (!data) return; + // const invited = umbUsersData.save(newUser); - const enabledKeys = umbUsersData.disable(data); + // console.log('invited', invited); - return res(ctx.status(200), ctx.json(enabledKeys)); - }), + // return res(ctx.status(200), ctx.json(invited)); + // }), - rest.post>('/umbraco/backoffice/users/updateUserGroup', async (req, res, ctx) => { - const data = await req.json(); - if (!data) return; + // rest.post>('/umbraco/backoffice/users/enable', async (req, res, ctx) => { + // const data = await req.json(); + // if (!data) return; - const userKeys = umbUsersData.updateUserGroup(data.userKeys, data.userGroupKey); + // const enabledKeys = umbUsersData.enable(data); - return res(ctx.status(200), ctx.json(userKeys)); - }), + // return res(ctx.status(200), ctx.json(enabledKeys)); + // }), - rest.post>('/umbraco/backoffice/users/delete', async (req, res, ctx) => { - const data = await req.json(); - if (!data) return; + // rest.post>('/umbraco/backoffice/users/disable', async (req, res, ctx) => { + // const data = await req.json(); + // if (!data) return; - const deletedKeys = umbUsersData.delete(data); + // const enabledKeys = umbUsersData.disable(data); - return res(ctx.status(200), ctx.json(deletedKeys)); - }), + // return res(ctx.status(200), ctx.json(enabledKeys)); + // }), + + // rest.post>('/umbraco/backoffice/users/updateUserGroup', async (req, res, ctx) => { + // const data = await req.json(); + // if (!data) return; + + // const userKeys = umbUsersData.updateUserGroup(data.userKeys, data.userGroupKey); + + // return res(ctx.status(200), ctx.json(userKeys)); + // }), + + // rest.post>('/umbraco/backoffice/users/delete', async (req, res, ctx) => { + // const data = await req.json(); + // if (!data) return; + + // const deletedKeys = umbUsersData.delete(data); + + // return res(ctx.status(200), ctx.json(deletedKeys)); + // }), ]; diff --git a/src/Umbraco.Web.UI.Client/src/core/modal/modal-element-picker-base.ts b/src/Umbraco.Web.UI.Client/src/core/modal/modal-element-picker-base.ts index 6cee4e6443..1cb4cacec2 100644 --- a/src/Umbraco.Web.UI.Client/src/core/modal/modal-element-picker-base.ts +++ b/src/Umbraco.Web.UI.Client/src/core/modal/modal-element-picker-base.ts @@ -4,7 +4,7 @@ import { UmbPickerModalData, UmbPickerModalResult } from '@umbraco-cms/backoffic // TODO: we should consider moving this into a class/context instead of an element. // So we don't have to extend an element to get basic picker/selection logic -export class UmbModalElementPickerBase extends UmbModalBaseElement, UmbPickerModalResult> { +export class UmbModalElementPickerBase extends UmbModalBaseElement, UmbPickerModalResult> { @property() selection: Array = []; From 79ccca905168a94a08b6ee5b767bdf19e5d9f388 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 26 Apr 2023 08:06:25 +0200 Subject: [PATCH 3/3] rename package schema --- src/Umbraco.Web.UI.Client/utils/move-libs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/utils/move-libs.js b/src/Umbraco.Web.UI.Client/utils/move-libs.js index 76c29dade7..08a4111d25 100644 --- a/src/Umbraco.Web.UI.Client/utils/move-libs.js +++ b/src/Umbraco.Web.UI.Client/utils/move-libs.js @@ -22,7 +22,7 @@ cpSync(`${srcDir}/package.json`, `${inputDir}/package.json`, { recursive: true } console.log(`Copied ${srcDir}/package.json to ${inputDir}/package.json`); cpSync(`${srcDir}/README.md`, `${inputDir}/README.md`, { recursive: true }); console.log(`Copied ${srcDir}/README.md to ${inputDir}/README.md`); -cpSync(`${inputDir}/umbraco-package-schema.json`, `${executableDir}/umbraco-json-schema.json`, { recursive: true }); +cpSync(`${inputDir}/umbraco-package-schema.json`, `${executableDir}/umbraco-package-schema.json`, { recursive: true }); console.log(`Copied ${inputDir}/umbraco-package-schema.json to ${executableDir}/umbraco-package-schema.json`); const libs = readdirSync(inputDir);