Skip to content

Commit

Permalink
Merge pull request #1027 from umbraco/feature/send-user-invite
Browse files Browse the repository at this point in the history
  • Loading branch information
nielslyngsoe authored Nov 28, 2023
2 parents 13d6f91 + 6b88a17 commit 11efe83
Show file tree
Hide file tree
Showing 50 changed files with 369 additions and 386 deletions.
8 changes: 5 additions & 3 deletions src/mocks/data/user/user.db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,15 @@ class UmbUserData extends UmbEntityData<UserResponseModel> {
* @param {InviteUserRequestModel} data
* @memberof UmbUserData
*/
invite(data: InviteUserRequestModel): void {
invite(data: InviteUserRequestModel) {
const invitedUser = {
status: UserStateModel.INVITED,
...data,
state: UserStateModel.INVITED,
};

this.createUser(invitedUser);
const response = this.createUser(invitedUser);

return { userId: response.userId };
}

filter(options: any): PagedUserResponseModel {
Expand Down
12 changes: 8 additions & 4 deletions src/mocks/handlers/user/invite.handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ import { UMB_SLUG } from './slug.js';
import { InviteUserRequestModel } from '@umbraco-cms/backoffice/backend-api';
import { umbracoPath } from '@umbraco-cms/backoffice/utils';

const inviteSlug = `${UMB_SLUG}/invite`;

export const handlers = [
rest.post<InviteUserRequestModel>(umbracoPath(`${UMB_SLUG}/invite`), async (req, res, ctx) => {
rest.post<InviteUserRequestModel>(umbracoPath(`${inviteSlug}`), async (req, res, ctx) => {
const data = await req.json();
if (!data) return;

umbUsersData.invite(data);
const { userId } = umbUsersData.invite(data);

return res(ctx.status(200));
if (!userId) return res(ctx.status(400));

return res(ctx.status(201), ctx.set('Location', userId));
}),

rest.post<any>(umbracoPath(`${UMB_SLUG}/invite/resend`), async (req, res, ctx) => {
rest.post<any>(umbracoPath(`${inviteSlug}/resend`), async (req, res, ctx) => {
const data = await req.json();
if (!data) return;

Expand Down
18 changes: 18 additions & 0 deletions src/packages/core/modal/modal-element.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,22 @@ export abstract class UmbModalBaseElement<
public set _value(value: ModalValueType) {
this.modalContext?.setValue(value);
}

/**
* Submits the modal
* @protected
* @memberof UmbModalBaseElement
*/
protected _submitModal() {
this.modalContext?.submit(this._value);
}

/**
* Rejects the modal
* @protected
* @memberof UmbModalBaseElement
*/
protected _rejectModal() {
this.modalContext?.reject();
}
}
2 changes: 0 additions & 2 deletions src/packages/core/modal/token/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export * from './examine-fields-settings-modal.token.js';
export * from './export-dictionary-modal.token.js';
export * from './icon-picker-modal.token.js';
export * from './import-dictionary-modal.token.js';
export * from './invite-user-modal.token.js';
export * from './language-picker-modal.token.js';
export * from './link-picker-modal.token.js';
export * from './media-tree-picker-modal.token.js';
Expand All @@ -37,4 +36,3 @@ export * from './data-type-picker-flow-modal.token.js';
export * from './data-type-picker-flow-data-type-picker-modal.token.js';
export * from './entity-user-permission-settings-modal.token.js';
export * from './permissions-modal.token.js';
export * from './resend-invite-to-user-modal.token.js';
4 changes: 2 additions & 2 deletions src/packages/core/modal/token/user-picker-modal.token.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UmbUserDetail } from '@umbraco-cms/backoffice/user';
import { UmbUserDetailModel } from '@umbraco-cms/backoffice/user';
import { UmbModalToken, UmbPickerModalData } from '@umbraco-cms/backoffice/modal';

export type UmbUserPickerModalData = UmbPickerModalData<UmbUserDetail>;
export type UmbUserPickerModalData = UmbPickerModalData<UmbUserDetailModel>;

export interface UmbUserPickerModalValue {
selection: Array<string | null>;
Expand Down
21 changes: 1 addition & 20 deletions src/packages/user/user/collection/action/manifests.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { UmbCreateUserCollectionAction } from './create-user.collection-action.js';
import { UmbInviteUserCollectionAction } from './invite-user.collection-action.js';
import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection';
import { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';

Expand All @@ -21,22 +20,4 @@ export const createManifest: ManifestTypes = {
],
};

export const inviteManifest: ManifestTypes = {
type: 'collectionAction',
kind: 'button',
name: 'Invite User Collection Action',
alias: 'Umb.CollectionAction.User.Invite',
api: UmbInviteUserCollectionAction,
weight: 100,
meta: {
label: 'Invite',
},
conditions: [
{
alias: UMB_COLLECTION_ALIAS_CONDITION,
match: 'Umb.Collection.User',
},
],
};

export const manifests = [createManifest, inviteManifest];
export const manifests = [createManifest];
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UMB_USER_ENTITY_TYPE, type UmbUserCollectionFilterModel, type UmbUserDetail } from '../../types.js';
import { type UmbUserCollectionFilterModel, type UmbUserDetailModel } from '../../types.js';
import { UMB_USER_ENTITY_TYPE } from '../../entity.js';
import { UmbCollectionDataSource, extendDataSourcePagedResponseData } from '@umbraco-cms/backoffice/repository';
import { UserResource } from '@umbraco-cms/backoffice/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
Expand All @@ -10,7 +11,7 @@ import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
* @class UmbUserCollectionServerDataSource
* @implements {UmbCollectionDataSource}
*/
export class UmbUserCollectionServerDataSource implements UmbCollectionDataSource<UmbUserDetail> {
export class UmbUserCollectionServerDataSource implements UmbCollectionDataSource<UmbUserDetailModel> {
#host: UmbControllerHost;

/**
Expand All @@ -30,7 +31,7 @@ export class UmbUserCollectionServerDataSource implements UmbCollectionDataSourc
*/
async getCollection(filter: UmbUserCollectionFilterModel) {
const response = await tryExecuteAndNotify(this.#host, UserResource.getUserFilter(filter));
return extendDataSourcePagedResponseData<UmbUserDetail>(response, {
return extendDataSourcePagedResponseData<UmbUserDetailModel>(response, {
entityType: UMB_USER_ENTITY_TYPE,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@ import {
} from '@umbraco-cms/backoffice/external/uui';
import { css, html, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { UmbDropdownElement } from '@umbraco-cms/backoffice/components';
import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
import {
UMB_CREATE_USER_MODAL,
UMB_INVITE_USER_MODAL,
UMB_MODAL_MANAGER_CONTEXT_TOKEN,
UmbModalManagerContext,
} from '@umbraco-cms/backoffice/modal';
import { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { UserGroupResponseModel, UserOrderModel, UserStateModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbUserGroupCollectionRepository } from '@umbraco-cms/backoffice/user-group';

Expand Down Expand Up @@ -72,10 +66,6 @@ export class UmbUserCollectionHeaderElement extends UmbLitElement {
this.#inputTimer = setTimeout(() => this.#collectionContext?.setFilter({ filter }), this.#inputTimerAmount);
}

#onInviteUserClick() {
this.#modalContext?.open(UMB_INVITE_USER_MODAL);
}

#onStateFilterChange(event: UUIBooleanInputEvent) {
event.stopPropagation();
const target = event.currentTarget as UUICheckboxElement;
Expand Down
7 changes: 5 additions & 2 deletions src/packages/user/user/collection/user-collection.context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { UmbUserCollectionFilterModel, UmbUserDetail } from '../types.js';
import { UmbUserCollectionFilterModel, UmbUserDetailModel } from '../types.js';
import { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection';
import { UserOrderModel, UserStateModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';

export class UmbUserCollectionContext extends UmbDefaultCollectionContext<UmbUserDetail, UmbUserCollectionFilterModel> {
export class UmbUserCollectionContext extends UmbDefaultCollectionContext<
UmbUserDetailModel,
UmbUserCollectionFilterModel
> {
constructor(host: UmbControllerHostElement) {
super(host, { pageSize: 50 });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getDisplayStateFromUserStatus } from '../../../../utils.js';
import { UmbUserCollectionContext } from '../../user-collection.context.js';
import { type UmbUserDetail } from '../../../types.js';
import { type UmbUserDetailModel } from '../../../types.js';
import { css, html, nothing, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection';
Expand All @@ -11,7 +11,7 @@ import { UmbUserGroupCollectionRepository } from '@umbraco-cms/backoffice/user-g
@customElement('umb-user-grid-collection-view')
export class UmbUserGridCollectionViewElement extends UmbLitElement {
@state()
private _users: Array<UmbUserDetail> = [];
private _users: Array<UmbUserDetailModel> = [];

@state()
private _selection: Array<string | null> = [];
Expand Down Expand Up @@ -53,11 +53,11 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
history.pushState(null, '', 'section/user-management/view/users/user/' + id); //TODO Change to a tag with href and make dynamic
}

#onSelect(user: UmbUserDetail) {
#onSelect(user: UmbUserDetailModel) {
this.#collectionContext?.selection.select(user.id ?? '');
}

#onDeselect(user: UmbUserDetail) {
#onDeselect(user: UmbUserDetailModel) {
this.#collectionContext?.selection.deselect(user.id ?? '');
}

Expand All @@ -74,7 +74,7 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
`;
}

#renderUserCard(user: UmbUserDetail) {
#renderUserCard(user: UmbUserDetailModel) {
return html`
<uui-card-user
.name=${user.name ?? 'Unnamed user'}
Expand All @@ -89,7 +89,7 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
`;
}

#renderUserTag(user: UmbUserDetail) {
#renderUserTag(user: UmbUserDetailModel) {
if (user.state && user.state === UserStateModel.ACTIVE) {
return nothing;
}
Expand All @@ -104,7 +104,7 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
</uui-tag>`;
}

#renderUserGroupNames(user: UmbUserDetail) {
#renderUserGroupNames(user: UmbUserDetailModel) {
const userGroupNames = this.#userGroups
.filter((userGroup) => user.userGroupIds?.includes(userGroup.id!))
.map((userGroup) => userGroup.name)
Expand All @@ -113,7 +113,7 @@ export class UmbUserGridCollectionViewElement extends UmbLitElement {
return html`<div>${userGroupNames}</div>`;
}

#renderUserLoginDate(user: UmbUserDetail) {
#renderUserLoginDate(user: UmbUserDetailModel) {
if (!user.lastLoginDate) {
return html`<div class="user-login-time">${`${user.name} ${this.localize.term('user_noLogin')}`}</div>`;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UmbUserCollectionContext } from '../../user-collection.context.js';
import type { UmbUserDetail } from '../../../types.js';
import type { UmbUserDetailModel } from '../../../types.js';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import {
Expand Down Expand Up @@ -57,7 +57,7 @@ export class UmbUserTableCollectionViewElement extends UmbLitElement {
#UmbUserGroupRepository = new UmbUserGroupRepository(this);

@state()
private _users: Array<UmbUserDetail> = [];
private _users: Array<UmbUserDetailModel> = [];

@state()
private _selection: Array<string | null> = [];
Expand Down
2 changes: 0 additions & 2 deletions src/packages/user/user/conditions/manifests.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { manifest as userAllowDisableActionManifest } from './user-allow-disable-action.condition.js';
import { manifest as userAllowEnableActionManifest } from './user-allow-enable-action.condition.js';
import { manifest as userAllowUnlockActionManifest } from './user-allow-unlock-action.condition.js';
import { manifest as userAllowResendInviteActionManifest } from './user-allow-resend-invite-action.condition.js';
import { manifest as userAllowDeleteActionManifest } from './user-allow-delete-action.condition.js';

export const manifests = [
userAllowDisableActionManifest,
userAllowEnableActionManifest,
userAllowUnlockActionManifest,
userAllowResendInviteActionManifest,
userAllowDeleteActionManifest,
];
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UmbUserDetail } from '../types.js';
import { UmbUserDetailModel } from '../types.js';
import { UmbUserWorkspaceContext } from '../workspace/user-workspace.context.js';
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
import { isCurrentUser } from '@umbraco-cms/backoffice/current-user';
Expand All @@ -13,7 +13,7 @@ export class UmbUserActionConditionBase extends UmbBaseController implements Umb
config: UmbConditionConfigBase;
permitted = false;
#onChange: () => void;
protected userData?: UmbUserDetail;
protected userData?: UmbUserDetailModel;

constructor(args: UmbConditionControllerArguments<UmbConditionConfigBase>) {
super(args.host);
Expand Down
22 changes: 1 addition & 21 deletions src/packages/user/user/entity-actions/manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ import {
UMB_CHANGE_USER_PASSWORD_REPOSITORY_ALIAS,
UMB_DISABLE_USER_REPOSITORY_ALIAS,
UMB_ENABLE_USER_REPOSITORY_ALIAS,
UMB_INVITE_USER_REPOSITORY_ALIAS,
UMB_UNLOCK_USER_REPOSITORY_ALIAS,
UMB_USER_REPOSITORY_ALIAS,
} from '../repository/manifests.js';
import { UMB_USER_ENTITY_TYPE } from '../index.js';
import { UMB_USER_ENTITY_TYPE } from '../entity.js';
import { UmbDisableUserEntityAction } from './disable/disable-user.action.js';
import { UmbEnableUserEntityAction } from './enable/enable-user.action.js';
import { UmbChangeUserPasswordEntityAction } from './change-password/change-user-password.action.js';
import { UmbUnlockUserEntityAction } from './unlock/unlock-user.action.js';
import { UmbResendInviteToUserEntityAction } from './resend-invite/resend-invite-to-user.action.js';
import { UmbDeleteEntityAction } from '@umbraco-cms/backoffice/entity-action';
import { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';

Expand Down Expand Up @@ -101,24 +99,6 @@ const entityActions: Array<ManifestTypes> = [
},
],
},
{
type: 'entityAction',
alias: 'Umb.EntityAction.User.ResendInvite',
name: 'Resend Invite User Entity Action',
weight: 500,
api: UmbResendInviteToUserEntityAction,
meta: {
icon: 'icon-message',
label: 'Resend Invite',
repositoryAlias: UMB_INVITE_USER_REPOSITORY_ALIAS,
entityTypes: [UMB_USER_ENTITY_TYPE],
},
conditions: [
{
alias: 'Umb.Condition.User.AllowResendInviteAction',
},
],
},
];

export const manifests = [...entityActions];
2 changes: 1 addition & 1 deletion src/packages/user/user/entity-bulk-actions/manifests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
UMB_UNLOCK_USER_REPOSITORY_ALIAS,
UMB_USER_REPOSITORY_ALIAS,
} from '../repository/manifests.js';
import { UMB_USER_ENTITY_TYPE } from '../types.js';
import { UMB_USER_ENTITY_TYPE } from '../entity.js';
import { UMB_USER_COLLECTION_ALIAS } from '../collection/manifests.js';
import { UmbEnableUserEntityBulkAction } from './enable/enable.action.js';
import { UmbSetGroupUserEntityBulkAction } from './set-group/set-group.action.js';
Expand Down
1 change: 1 addition & 0 deletions src/packages/user/user/entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const UMB_USER_ENTITY_TYPE = 'user';
1 change: 1 addition & 0 deletions src/packages/user/user/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './components/index.js';
export * from './invite/index.js';
export * from './repository/index.js';
export * from './types.js';
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { UMB_INVITE_USER_MODAL } from '../modal/index.js';
import { UmbCollectionActionBase } from '@umbraco-cms/backoffice/collection';
import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import {
UMB_INVITE_USER_MODAL,
UMB_MODAL_MANAGER_CONTEXT_TOKEN,
UmbModalManagerContext,
} from '@umbraco-cms/backoffice/modal';
import { UMB_MODAL_MANAGER_CONTEXT_TOKEN, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';

export class UmbInviteUserCollectionAction extends UmbCollectionActionBase {
#modalManagerContext: UmbModalManagerContext | undefined;
Expand Down
23 changes: 23 additions & 0 deletions src/packages/user/user/invite/collection-action/manifests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { UmbInviteUserCollectionAction } from './invite-user.collection-action.js';
import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection';
import { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';

export const inviteManifest: ManifestTypes = {
type: 'collectionAction',
kind: 'button',
name: 'Invite User Collection Action',
alias: 'Umb.CollectionAction.User.Invite',
api: UmbInviteUserCollectionAction,
weight: 100,
meta: {
label: 'Invite',
},
conditions: [
{
alias: UMB_COLLECTION_ALIAS_CONDITION,
match: 'Umb.Collection.User',
},
],
};

export const manifests = [inviteManifest];
Loading

0 comments on commit 11efe83

Please sign in to comment.