Merge pull request #1571 from umbraco/feature/user-profile-actions-default

Feature/user-profile-actions-default
This commit is contained in:
Lee Kelleher
2024-04-10 18:39:44 +01:00
committed by GitHub
11 changed files with 171 additions and 117 deletions

View File

@@ -12,6 +12,7 @@ export class UmbCurrentUserContext extends UmbContextBase<UmbCurrentUserContext>
#currentUser = new UmbObjectState<UmbCurrentUserModel | undefined>(undefined);
readonly currentUser = this.#currentUser.asObservable();
readonly unique = this.#currentUser.asObservablePart((user) => user?.unique);
readonly languageIsoCode = this.#currentUser.asObservablePart((user) => user?.languageIsoCode);
#authContext?: typeof UMB_AUTH_CONTEXT.TYPE;

View File

@@ -1,25 +0,0 @@
import { html, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@customElement('umb-external-login-providers-user-profile-app')
export class UmbExternalLoginProvidersUserProfileAppElement extends UmbLitElement {
render() {
return html`
<uui-box>
<b slot="headline">External login providers</b>
<umb-extension-slot id="externalLoginProviders" type="externalLoginProvider"></umb-extension-slot>
</uui-box>
`;
}
static styles = [UmbTextStyles];
}
export default UmbExternalLoginProvidersUserProfileAppElement;
declare global {
interface HTMLElementTagNameMap {
'umb-external-login-providers-user-profile-app': UmbExternalLoginProvidersUserProfileAppElement;
}
}

View File

@@ -1,16 +0,0 @@
import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry';
export const userProfileApps: Array<ManifestUserProfileApp> = [
{
type: 'userProfileApp',
alias: 'Umb.UserProfileApp.CurrentUser.ExternalLoginProviders',
name: 'External Login Providers User Profile App',
element: () => import('./external-login-providers-user-profile-app.element.js'),
weight: 700,
meta: {
label: 'External Login Providers User Profile App',
pathname: 'externalLoginProviders',
},
},
];
export const manifests = [...userProfileApps];

View File

@@ -1,6 +1,5 @@
import { manifest as actionDefaultKindManifest } from './action/default.kind.js';
import { manifests as modalManifests } from './modals/manifests.js';
import { manifests as externalLoginProviderManifests } from './external-login/manifests.js';
import { manifests as historyManifests } from './history/manifests.js';
import { manifests as mfaLoginProviderManifests } from './mfa-login/manifests.js';
import { manifests as profileManifests } from './profile/manifests.js';
@@ -31,7 +30,6 @@ export const headerApps: Array<ManifestTypes> = [
export const manifests = [
actionDefaultKindManifest,
...externalLoginProviderManifests,
...headerApps,
...historyManifests,
...mfaLoginProviderManifests,

View File

@@ -0,0 +1,18 @@
import { UMB_CURRENT_USER_MFA_MODAL } from '../modals/current-user-mfa/current-user-mfa-modal.token.js';
import { UmbActionBase } from '@umbraco-cms/backoffice/action';
import type { UmbCurrentUserAction, UmbCurrentUserActionArgs } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
export class UmbConfigureMfaProvidersApi<ArgsMetaType = never>
extends UmbActionBase<UmbCurrentUserActionArgs<ArgsMetaType>>
implements UmbCurrentUserAction<ArgsMetaType>
{
async getHref() {
return undefined;
}
async execute() {
const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
await modalManagerContext.open(this, UMB_CURRENT_USER_MFA_MODAL).onSubmit();
}
}

View File

@@ -1,16 +1,24 @@
import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry';
import { UmbConfigureMfaProvidersApi } from './configure-mfa-providers-action.js';
import type { ManifestCurrentUserActionDefaultKind } from '@umbraco-cms/backoffice/extension-registry';
export const userProfileApps: Array<ManifestUserProfileApp> = [
export const userProfileApps: Array<ManifestCurrentUserActionDefaultKind> = [
{
type: 'userProfileApp',
alias: 'Umb.UserProfileApp.CurrentUser.MfaLoginProviders',
name: 'MFA Login Providers User Profile App',
element: () => import('./mfa-providers-user-profile-app.element.js'),
type: 'currentUserAction',
kind: 'default',
alias: 'Umb.CurrentUser.App.MfaLoginProviders',
name: 'MFA Login Providers Current User App',
weight: 800,
api: UmbConfigureMfaProvidersApi,
meta: {
label: 'Two-Factor Authentication',
pathname: 'mfa-providers',
label: '#user_configureTwoFactor',
icon: 'icon-rectangle-ellipsis',
look: 'secondary',
},
conditions: [
{
alias: 'Umb.Condition.User.AllowMfaAction',
},
],
},
];
export const manifests = [...userProfileApps];

View File

@@ -6,8 +6,8 @@ import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
@customElement('umb-mfa-providers-user-profile-app')
export class UmbMfaProvidersUserProfileAppElement extends UmbLitElement {
@customElement('umb-mfa-providers-current-user-app')
export class UmbMfaProvidersCurrentUserAppElement extends UmbLitElement {
@state()
_hasProviders = false;
@@ -26,11 +26,14 @@ export class UmbMfaProvidersUserProfileAppElement extends UmbLitElement {
}
return html`
<uui-box .headline=${this.localize.term('member_2fa')}>
<uui-button type="button" look="primary" @click=${this.#onClick}>
<umb-localize key="user_configureTwoFactor">Configure Two Factor</umb-localize>
</uui-button>
</uui-box>
<uui-button
type="button"
look="primary"
label="${this.localize.term('user_configureTwoFactor')}"
@click=${this.#onClick}>
<uui-icon name="icon-rectangle-ellipsis"></uui-icon>
<umb-localize key="user_configureTwoFactor">Configure Two Factor</umb-localize>
</uui-button>
`;
}
@@ -42,10 +45,10 @@ export class UmbMfaProvidersUserProfileAppElement extends UmbLitElement {
static styles = [UmbTextStyles];
}
export default UmbMfaProvidersUserProfileAppElement;
export default UmbMfaProvidersCurrentUserAppElement;
declare global {
interface HTMLElementTagNameMap {
'umb-mfa-providers-user-profile-app': UmbMfaProvidersUserProfileAppElement;
'umb-mfa-providers-current-user-app': UmbMfaProvidersCurrentUserAppElement;
}
}

View File

@@ -0,0 +1,43 @@
import { UMB_CURRENT_USER_CONTEXT } from '../current-user.context.js';
import { UmbActionBase } from '@umbraco-cms/backoffice/action';
import type { UmbCurrentUserAction, UmbCurrentUserActionArgs } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UMB_CHANGE_PASSWORD_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
export class UmbChangePasswordCurrentUserAction<ArgsMetaType = never>
extends UmbActionBase<UmbCurrentUserActionArgs<ArgsMetaType>>
implements UmbCurrentUserAction<ArgsMetaType>
{
#unique?: string;
constructor(host: UmbControllerHost, args: UmbCurrentUserActionArgs<ArgsMetaType>) {
super(host, args);
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (context) => {
this.observe(
context.unique,
(unique) => {
this.#unique = unique;
},
'umbEditCurrentUserActionObserver',
);
});
}
async getHref() {
return undefined;
}
async execute() {
if (!this.#unique) return;
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
modalManager.open(this, UMB_CHANGE_PASSWORD_MODAL, {
data: {
user: {
unique: this.#unique,
},
},
});
}
}

View File

@@ -1,65 +1,12 @@
import { html, customElement, state, css } from '@umbraco-cms/backoffice/external/lit';
import { html, customElement, css } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_CHANGE_PASSWORD_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UMB_CURRENT_USER_CONTEXT, type UmbCurrentUserModel } from '@umbraco-cms/backoffice/current-user';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
@customElement('umb-current-user-profile-user-profile-app')
export class UmbCurrentUserProfileUserProfileAppElement extends UmbLitElement {
@state()
private _currentUser?: UmbCurrentUserModel;
#currentUserContext?: typeof UMB_CURRENT_USER_CONTEXT.TYPE;
constructor() {
super();
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (instance) => {
this.#currentUserContext = instance;
this._observeCurrentUser();
});
}
private async _observeCurrentUser() {
if (!this.#currentUserContext) return;
this.observe(
this.#currentUserContext.currentUser,
(currentUser) => {
this._currentUser = currentUser;
},
'umbCurrentUserObserver',
);
}
private _edit() {
if (!this._currentUser) return;
history.pushState(null, '', 'section/user-management/view/users/user/edit/' + this._currentUser.unique); //TODO Change to a tag with href and make dynamic
//TODO Implement modal routing for the current-user-modal, so that the modal closes when navigating to the edit profile page
}
async #changePassword() {
if (!this._currentUser) throw new Error('Current User not found');
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
modalManager.open(this, UMB_CHANGE_PASSWORD_MODAL, {
data: {
user: {
unique: this._currentUser.unique,
},
},
});
}
render() {
return html`
<uui-box .headline=${this.localize.term('user_yourProfile')}>
<uui-button look="primary" label=${this.localize.term('general_edit')} @click=${this._edit}>
${this.localize.term('general_edit')}
</uui-button>
<uui-button look="primary" label=${this.localize.term('general_changePassword')} @click=${this.#changePassword}>
${this.localize.term('general_changePassword')}
</uui-button>
<umb-extension-with-api-slot id="actions" type="currentUserAction"></umb-extension-with-api-slot>
</uui-box>
`;

View File

@@ -0,0 +1,38 @@
import { UMB_CURRENT_USER_CONTEXT } from '../current-user.context.js';
import { UmbActionBase } from '@umbraco-cms/backoffice/action';
import type { UmbCurrentUserAction, UmbCurrentUserActionArgs } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export class UmbEditCurrentUserAction<ArgsMetaType = never>
extends UmbActionBase<UmbCurrentUserActionArgs<ArgsMetaType>>
implements UmbCurrentUserAction<ArgsMetaType>
{
#init;
#unique?: string;
constructor(host: UmbControllerHost, args: UmbCurrentUserActionArgs<ArgsMetaType>) {
super(host, args);
this.#init = new Promise<void>((res) => {
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (context) => {
this.observe(
context.unique,
(unique) => {
this.#unique = unique;
res();
},
'umbEditCurrentUserActionObserver',
);
});
});
}
async getHref() {
await this.#init;
return `section/user-management/view/users/user/edit/${this.#unique}`;
}
async execute() {
return;
}
}

View File

@@ -1,6 +1,11 @@
import type { ManifestUserProfileApp } from '@umbraco-cms/backoffice/extension-registry';
import { UmbChangePasswordCurrentUserAction } from './change-password-current-user.action.js';
import { UmbEditCurrentUserAction } from './edit-current-user.action.js';
import type {
ManifestCurrentUserActionDefaultKind,
ManifestUserProfileApp,
} from '@umbraco-cms/backoffice/extension-registry';
export const userProfileApps: Array<ManifestUserProfileApp> = [
const userProfileApps: Array<ManifestUserProfileApp> = [
{
type: 'userProfileApp',
alias: 'Umb.UserProfileApp.CurrentUser.Profile',
@@ -13,4 +18,38 @@ export const userProfileApps: Array<ManifestUserProfileApp> = [
},
},
];
export const manifests = [...userProfileApps];
const currentUserActions: Array<ManifestCurrentUserActionDefaultKind> = [
{
type: 'currentUserAction',
kind: 'default',
alias: 'Umb.CurrentUser.Button.Edit',
name: 'Current User Edit Button',
weight: 1000,
api: UmbEditCurrentUserAction,
meta: {
label: '#general_edit',
icon: 'edit',
},
conditions: [
{
alias: 'Umb.Condition.SectionUserPermission',
match: 'Umb.Section.Users',
},
],
},
{
type: 'currentUserAction',
kind: 'default',
alias: 'Umb.CurrentUser.Button.ChangePassword',
name: 'Current User Change Password Button',
weight: 900,
api: UmbChangePasswordCurrentUserAction,
meta: {
label: '#general_changePassword',
icon: 'lock',
},
},
];
export const manifests = [...userProfileApps, ...currentUserActions];