diff --git a/apps/web/src/app/admin-console/common/base-members.component.ts b/apps/web/src/app/admin-console/common/base-members.component.ts deleted file mode 100644 index 5ecf4269a1a..00000000000 --- a/apps/web/src/app/admin-console/common/base-members.component.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { Directive } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { FormControl } from "@angular/forms"; -import { firstValueFrom, lastValueFrom, debounceTime, combineLatest, BehaviorSubject } from "rxjs"; - -import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; -import { - OrganizationUserStatusType, - OrganizationUserType, - ProviderUserStatusType, - ProviderUserType, -} from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; - -import { OrganizationUserView } from "../organizations/core/views/organization-user.view"; -import { UserConfirmComponent } from "../organizations/manage/user-confirm.component"; -import { MemberActionResult } from "../organizations/members/services/member-actions/member-actions.service"; - -import { PeopleTableDataSource, peopleFilter } from "./people-table-data-source"; - -export type StatusType = OrganizationUserStatusType | ProviderUserStatusType; -export type UserViewTypes = ProviderUserUserDetailsResponse | OrganizationUserView; - -/** - * A refactored copy of BasePeopleComponent, using the component library table and other modern features. - * This will replace BasePeopleComponent once all subclasses have been changed over to use this class. - */ -@Directive() -export abstract class BaseMembersComponent { - /** - * Shows a banner alerting the admin that users need to be confirmed. - */ - get showConfirmUsers(): boolean { - return ( - this.dataSource.activeUserCount > 1 && - this.dataSource.confirmedUserCount > 0 && - this.dataSource.confirmedUserCount < 3 && - this.dataSource.acceptedUserCount > 0 - ); - } - - get showBulkConfirmUsers(): boolean { - return this.dataSource - .getCheckedUsers() - .every((member) => member.status == this.userStatusType.Accepted); - } - - get showBulkReinviteUsers(): boolean { - return this.dataSource - .getCheckedUsers() - .every((member) => member.status == this.userStatusType.Invited); - } - - abstract userType: typeof OrganizationUserType | typeof ProviderUserType; - abstract userStatusType: typeof OrganizationUserStatusType | typeof ProviderUserStatusType; - - protected abstract dataSource: PeopleTableDataSource; - - firstLoaded: boolean = false; - - /** - * The currently selected status filter, or undefined to show all active users. - */ - status?: StatusType; - - /** - * The currently executing promise - used to avoid multiple user actions executing at once. - */ - actionPromise?: Promise; - - protected searchControl = new FormControl("", { nonNullable: true }); - protected statusToggle = new BehaviorSubject(undefined); - - constructor( - protected apiService: ApiService, - protected i18nService: I18nService, - protected keyService: KeyService, - protected validationService: ValidationService, - protected logService: LogService, - protected userNamePipe: UserNamePipe, - protected dialogService: DialogService, - protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, - protected toastService: ToastService, - ) { - // Connect the search input and status toggles to the table dataSource filter - combineLatest([this.searchControl.valueChanges.pipe(debounceTime(200)), this.statusToggle]) - .pipe(takeUntilDestroyed()) - .subscribe( - ([searchText, status]) => (this.dataSource.filter = peopleFilter(searchText, status)), - ); - } - - abstract edit(user: UserView, organization?: Organization): void; - abstract getUsers(organization?: Organization): Promise | UserView[]>; - abstract removeUser(id: string, organization?: Organization): Promise; - abstract reinviteUser(id: string, organization?: Organization): Promise; - abstract confirmUser( - user: UserView, - publicKey: Uint8Array, - organization?: Organization, - ): Promise; - abstract invite(organization?: Organization): void; - - async load(organization?: Organization) { - // Load new users from the server - const response = await this.getUsers(organization); - - // GetUsers can return a ListResponse or an Array - if (response instanceof ListResponse) { - this.dataSource.data = response.data != null && response.data.length > 0 ? response.data : []; - } else if (Array.isArray(response)) { - this.dataSource.data = response; - } - - this.firstLoaded = true; - } - - protected async removeUserConfirmationDialog(user: UserView) { - return this.dialogService.openSimpleDialog({ - title: this.userNamePipe.transform(user), - content: { key: "removeUserConfirmation" }, - type: "warning", - }); - } - - async remove(user: UserView, organization?: Organization) { - const confirmed = await this.removeUserConfirmationDialog(user); - if (!confirmed) { - return false; - } - - this.actionPromise = this.removeUser(user.id, organization); - try { - const result = await this.actionPromise; - if (result.success) { - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t("removedUserId", this.userNamePipe.transform(user)), - }); - this.dataSource.removeUser(user); - } else { - throw new Error(result.error); - } - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = undefined; - } - - async reinvite(user: UserView, organization?: Organization) { - if (this.actionPromise != null) { - return; - } - - this.actionPromise = this.reinviteUser(user.id, organization); - try { - const result = await this.actionPromise; - if (result.success) { - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t("hasBeenReinvited", this.userNamePipe.transform(user)), - }); - } else { - throw new Error(result.error); - } - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = undefined; - } - - async confirm(user: UserView, organization?: Organization) { - const confirmUser = async (publicKey: Uint8Array) => { - try { - this.actionPromise = this.confirmUser(user, publicKey, organization); - const result = await this.actionPromise; - if (result.success) { - user.status = this.userStatusType.Confirmed; - this.dataSource.replaceUser(user); - - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t("hasBeenConfirmed", this.userNamePipe.transform(user)), - }); - } else { - throw new Error(result.error); - } - } catch (e) { - this.validationService.showError(e); - throw e; - } finally { - this.actionPromise = undefined; - } - }; - - if (this.actionPromise != null) { - return; - } - - try { - const publicKeyResponse = await this.apiService.getUserPublicKey(user.userId); - const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); - - const autoConfirm = await firstValueFrom( - this.organizationManagementPreferencesService.autoConfirmFingerPrints.state$, - ); - if (user == null) { - throw new Error("Cannot confirm null user."); - } - if (autoConfirm == null || !autoConfirm) { - const dialogRef = UserConfirmComponent.open(this.dialogService, { - data: { - name: this.userNamePipe.transform(user), - userId: user.userId, - publicKey: publicKey, - confirmUser: () => confirmUser(publicKey), - }, - }); - await lastValueFrom(dialogRef.closed); - - return; - } - - try { - const fingerprint = await this.keyService.getFingerprint(user.userId, publicKey); - this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`); - } catch (e) { - this.logService.error(e); - } - await confirmUser(publicKey); - } catch (e) { - this.logService.error(`Handled exception: ${e}`); - } - } -} diff --git a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts index 03130d0b946..788d01695b0 100644 --- a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts @@ -2,11 +2,8 @@ // @ts-strict-ignore import { Component, Inject, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; @@ -17,8 +14,6 @@ export type UserConfirmDialogData = { name: string; userId: string; publicKey: Uint8Array; - // @TODO remove this when doing feature flag cleanup for members component refactor. - confirmUser?: (publicKey: Uint8Array) => Promise; }; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -46,7 +41,6 @@ export class UserConfirmComponent implements OnInit { private keyService: KeyService, private logService: LogService, private organizationManagementPreferencesService: OrganizationManagementPreferencesService, - private configService: ConfigService, ) { this.name = data.name; this.userId = data.userId; @@ -76,13 +70,6 @@ export class UserConfirmComponent implements OnInit { await this.organizationManagementPreferencesService.autoConfirmFingerPrints.set(true); } - const membersComponentRefactorEnabled = await firstValueFrom( - this.configService.getFeatureFlag$(FeatureFlag.MembersComponentRefactor), - ); - if (!membersComponentRefactorEnabled) { - await this.data.confirmUser(this.publicKey); - } - this.dialogRef.close(true); }; diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 6848f76286f..43520449535 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -195,9 +195,9 @@ export class MemberDialogComponent implements OnDestroy { private accountService: AccountService, organizationService: OrganizationService, private toastService: ToastService, - private configService: ConfigService, private deleteManagedMemberWarningService: DeleteManagedMemberWarningService, private organizationUserService: OrganizationUserService, + private configService: ConfigService, ) { this.organization$ = accountService.activeAccount$.pipe( getUserId, diff --git a/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.html b/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.html deleted file mode 100644 index 65bab31c728..00000000000 --- a/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.html +++ /dev/null @@ -1,495 +0,0 @@ -@let organization = this.organization(); -@if (organization) { - - - - - - - - -
- - - {{ "all" | i18n }} - {{ - allCount - }} - - - - {{ "invited" | i18n }} - {{ - invitedCount - }} - - - - {{ "needsConfirmation" | i18n }} - {{ - acceptedUserCount - }} - - - - {{ "revoked" | i18n }} - {{ - revokedCount - }} - - -
- - - {{ "loading" | i18n }} - - -

{{ "noMembersInList" | i18n }}

- - - {{ "usersNeedConfirmed" | i18n }} - - - - - - - - - - - {{ "name" | i18n }} - {{ (organization.useGroups ? "groups" : "collections") | i18n }} - {{ "role" | i18n }} - {{ "policies" | i18n }} - -
- - -
- - - - - - - - - - - - - - - -
- - - - - - - -
- -
-
- - - {{ "invited" | i18n }} - - - {{ "needsConfirmation" | i18n }} - - - {{ "revoked" | i18n }} - -
-
- {{ u.email }} -
-
-
- -
- - -
- -
-
- {{ u.name ?? u.email }} - - {{ "invited" | i18n }} - - - {{ "needsConfirmation" | i18n }} - - - {{ "revoked" | i18n }} - -
-
- {{ u.email }} -
-
-
- -
- - - - - - - - - - - - - - - {{ u.type | userType }} - - - - - {{ u.type | userType }} - - - - - - - {{ "userUsingTwoStep" | i18n }} - - @let resetPasswordPolicyEnabled = resetPasswordPolicyEnabled$ | async; - - - {{ "enrolledAccountRecovery" | i18n }} - - - -
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-} diff --git a/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.ts b/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.ts deleted file mode 100644 index 1f1e19e2a6f..00000000000 --- a/apps/web/src/app/admin-console/organizations/members/deprecated_members.component.ts +++ /dev/null @@ -1,624 +0,0 @@ -import { Component, computed, Signal } from "@angular/core"; -import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop"; -import { ActivatedRoute } from "@angular/router"; -import { - combineLatest, - concatMap, - filter, - firstValueFrom, - from, - map, - merge, - Observable, - shareReplay, - switchMap, - take, -} from "rxjs"; - -import { OrganizationUserUserDetailsResponse } from "@bitwarden/admin-console/common"; -import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { - OrganizationUserStatusType, - OrganizationUserType, - PolicyType, -} from "@bitwarden/common/admin-console/enums"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { OrganizationMetadataServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-metadata.service.abstraction"; -import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { getById } from "@bitwarden/common/platform/misc"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; -import { UserId } from "@bitwarden/user-core"; -import { BillingConstraintService } from "@bitwarden/web-vault/app/billing/members/billing-constraint/billing-constraint.service"; -import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services"; - -import { BaseMembersComponent } from "../../common/base-members.component"; -import { - CloudBulkReinviteLimit, - MaxCheckedCount, - PeopleTableDataSource, -} from "../../common/people-table-data-source"; -import { OrganizationUserView } from "../core/views/organization-user.view"; - -import { AccountRecoveryDialogResultType } from "./components/account-recovery/account-recovery-dialog.component"; -import { MemberDialogResult, MemberDialogTab } from "./components/member-dialog"; -import { - MemberDialogManagerService, - MemberExportService, - OrganizationMembersService, -} from "./services"; -import { DeleteManagedMemberWarningService } from "./services/delete-managed-member/delete-managed-member-warning.service"; -import { - MemberActionsService, - MemberActionResult, -} from "./services/member-actions/member-actions.service"; - -class MembersTableDataSource extends PeopleTableDataSource { - protected statusType = OrganizationUserStatusType; -} - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - templateUrl: "deprecated_members.component.html", - standalone: false, -}) -export class MembersComponent extends BaseMembersComponent { - userType = OrganizationUserType; - userStatusType = OrganizationUserStatusType; - memberTab = MemberDialogTab; - protected dataSource: MembersTableDataSource; - - readonly organization: Signal; - status: OrganizationUserStatusType | undefined; - - private userId$: Observable = this.accountService.activeAccount$.pipe(getUserId); - - resetPasswordPolicyEnabled$: Observable; - - protected readonly canUseSecretsManager: Signal = computed( - () => this.organization()?.useSecretsManager ?? false, - ); - protected readonly showUserManagementControls: Signal = computed( - () => this.organization()?.canManageUsers ?? false, - ); - protected billingMetadata$: Observable; - - // Fixed sizes used for cdkVirtualScroll - protected rowHeight = 66; - protected rowHeightClass = `tw-h-[66px]`; - - constructor( - apiService: ApiService, - i18nService: I18nService, - organizationManagementPreferencesService: OrganizationManagementPreferencesService, - keyService: KeyService, - validationService: ValidationService, - logService: LogService, - userNamePipe: UserNamePipe, - dialogService: DialogService, - toastService: ToastService, - private route: ActivatedRoute, - protected deleteManagedMemberWarningService: DeleteManagedMemberWarningService, - private organizationWarningsService: OrganizationWarningsService, - private memberActionsService: MemberActionsService, - private memberDialogManager: MemberDialogManagerService, - protected billingConstraint: BillingConstraintService, - protected memberService: OrganizationMembersService, - private organizationService: OrganizationService, - private accountService: AccountService, - private policyService: PolicyService, - private policyApiService: PolicyApiServiceAbstraction, - private organizationMetadataService: OrganizationMetadataServiceAbstraction, - private memberExportService: MemberExportService, - private environmentService: EnvironmentService, - ) { - super( - apiService, - i18nService, - keyService, - validationService, - logService, - userNamePipe, - dialogService, - organizationManagementPreferencesService, - toastService, - ); - - this.dataSource = new MembersTableDataSource(this.environmentService); - - const organization$ = this.route.params.pipe( - concatMap((params) => - this.userId$.pipe( - switchMap((userId) => - this.organizationService.organizations$(userId).pipe(getById(params.organizationId)), - ), - filter((organization): organization is Organization => organization != null), - shareReplay({ refCount: true, bufferSize: 1 }), - ), - ), - ); - - this.organization = toSignal(organization$); - - const policies$ = combineLatest([this.userId$, organization$]).pipe( - switchMap(([userId, organization]) => - organization.isProviderUser - ? from(this.policyApiService.getPolicies(organization.id)).pipe( - map((response) => Policy.fromListResponse(response)), - ) - : this.policyService.policies$(userId), - ), - ); - - this.resetPasswordPolicyEnabled$ = combineLatest([organization$, policies$]).pipe( - map( - ([organization, policies]) => - policies - .filter((policy) => policy.type === PolicyType.ResetPassword) - .find((p) => p.organizationId === organization.id)?.enabled ?? false, - ), - ); - - combineLatest([this.route.queryParams, organization$]) - .pipe( - concatMap(async ([qParams, organization]) => { - await this.load(organization!); - - this.searchControl.setValue(qParams.search); - - if (qParams.viewEvents != null) { - const user = this.dataSource.data.filter((u) => u.id === qParams.viewEvents); - if (user.length > 0 && user[0].status === OrganizationUserStatusType.Confirmed) { - this.openEventsDialog(user[0], organization!); - } - } - }), - takeUntilDestroyed(), - ) - .subscribe(); - - organization$ - .pipe( - switchMap((organization) => - merge( - this.organizationWarningsService.showInactiveSubscriptionDialog$(organization), - this.organizationWarningsService.showSubscribeBeforeFreeTrialEndsDialog$(organization), - ), - ), - takeUntilDestroyed(), - ) - .subscribe(); - - this.billingMetadata$ = organization$.pipe( - switchMap((organization) => - this.organizationMetadataService.getOrganizationMetadata$(organization.id), - ), - shareReplay({ bufferSize: 1, refCount: false }), - ); - - // Stripe is slow, so kick this off in the background but without blocking page load. - // Anyone who needs it will still await the first emission. - this.billingMetadata$.pipe(take(1), takeUntilDestroyed()).subscribe(); - } - - override async load(organization: Organization) { - await super.load(organization); - } - - async getUsers(organization: Organization): Promise { - return await this.memberService.loadUsers(organization); - } - - async removeUser(id: string, organization: Organization): Promise { - return await this.memberActionsService.removeUser(organization, id); - } - - async revokeUser(id: string, organization: Organization): Promise { - return await this.memberActionsService.revokeUser(organization, id); - } - - async restoreUser(id: string, organization: Organization): Promise { - return await this.memberActionsService.restoreUser(organization, id); - } - - async reinviteUser(id: string, organization: Organization): Promise { - return await this.memberActionsService.reinviteUser(organization, id); - } - - async confirmUser( - user: OrganizationUserView, - publicKey: Uint8Array, - organization: Organization, - ): Promise { - return await this.memberActionsService.confirmUser(user, publicKey, organization); - } - - async revoke(user: OrganizationUserView, organization: Organization) { - const confirmed = await this.revokeUserConfirmationDialog(user); - - if (!confirmed) { - return false; - } - - this.actionPromise = this.revokeUser(user.id, organization); - try { - const result = await this.actionPromise; - if (result.success) { - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t("revokedUserId", this.userNamePipe.transform(user)), - }); - await this.load(organization); - } else { - throw new Error(result.error); - } - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = undefined; - } - - async restore(user: OrganizationUserView, organization: Organization) { - this.actionPromise = this.restoreUser(user.id, organization); - try { - const result = await this.actionPromise; - if (result.success) { - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t("restoredUserId", this.userNamePipe.transform(user)), - }); - await this.load(organization); - } else { - throw new Error(result.error); - } - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = undefined; - } - - allowResetPassword( - orgUser: OrganizationUserView, - organization: Organization, - orgResetPasswordPolicyEnabled: boolean, - ): boolean { - return this.memberActionsService.allowResetPassword( - orgUser, - organization, - orgResetPasswordPolicyEnabled, - ); - } - - showEnrolledStatus( - orgUser: OrganizationUserUserDetailsResponse, - organization: Organization, - orgResetPasswordPolicyEnabled: boolean, - ): boolean { - return ( - organization.useResetPassword && - orgUser.resetPasswordEnrolled && - orgResetPasswordPolicyEnabled - ); - } - - private async handleInviteDialog(organization: Organization) { - const billingMetadata = await firstValueFrom(this.billingMetadata$); - const allUserEmails = this.dataSource.data?.map((user) => user.email) ?? []; - - const result = await this.memberDialogManager.openInviteDialog( - organization, - billingMetadata, - allUserEmails, - ); - - if (result === MemberDialogResult.Saved) { - await this.load(organization); - } - } - - async invite(organization: Organization) { - const billingMetadata = await firstValueFrom(this.billingMetadata$); - const seatLimitResult = this.billingConstraint.checkSeatLimit(organization, billingMetadata); - if (!(await this.billingConstraint.seatLimitReached(seatLimitResult, organization))) { - await this.handleInviteDialog(organization); - this.organizationMetadataService.refreshMetadataCache(); - } - } - - async edit( - user: OrganizationUserView, - organization: Organization, - initialTab: MemberDialogTab = MemberDialogTab.Role, - ) { - const billingMetadata = await firstValueFrom(this.billingMetadata$); - - const result = await this.memberDialogManager.openEditDialog( - user, - organization, - billingMetadata, - initialTab, - ); - - switch (result) { - case MemberDialogResult.Deleted: - this.dataSource.removeUser(user); - break; - case MemberDialogResult.Saved: - case MemberDialogResult.Revoked: - case MemberDialogResult.Restored: - await this.load(organization); - break; - } - } - - async bulkRemove(organization: Organization) { - if (this.actionPromise != null) { - return; - } - - const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); - - await this.memberDialogManager.openBulkRemoveDialog(organization, users); - this.organizationMetadataService.refreshMetadataCache(); - await this.load(organization); - } - - async bulkDelete(organization: Organization) { - if (this.actionPromise != null) { - return; - } - - const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); - - await this.memberDialogManager.openBulkDeleteDialog(organization, users); - await this.load(organization); - } - - async bulkRevoke(organization: Organization) { - await this.bulkRevokeOrRestore(true, organization); - } - - async bulkRestore(organization: Organization) { - await this.bulkRevokeOrRestore(false, organization); - } - - async bulkRevokeOrRestore(isRevoking: boolean, organization: Organization) { - if (this.actionPromise != null) { - return; - } - - const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); - - await this.memberDialogManager.openBulkRestoreRevokeDialog(organization, users, isRevoking); - await this.load(organization); - } - - async bulkReinvite(organization: Organization) { - if (this.actionPromise != null) { - return; - } - - let users: OrganizationUserView[]; - if (this.dataSource.isIncreasedBulkLimitEnabled()) { - users = this.dataSource.getCheckedUsersInVisibleOrder(); - } else { - users = this.dataSource.getCheckedUsers(); - } - - const allInvitedUsers = users.filter((u) => u.status === OrganizationUserStatusType.Invited); - - // Capture the original count BEFORE enforcing the limit - const originalInvitedCount = allInvitedUsers.length; - - // When feature flag is enabled, limit invited users and uncheck the excess - let filteredUsers: OrganizationUserView[]; - if (this.dataSource.isIncreasedBulkLimitEnabled()) { - filteredUsers = this.dataSource.limitAndUncheckExcess( - allInvitedUsers, - CloudBulkReinviteLimit, - ); - } else { - filteredUsers = allInvitedUsers; - } - - if (filteredUsers.length <= 0) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("noSelectedUsersApplicable"), - }); - return; - } - - try { - const result = await this.memberActionsService.bulkReinvite(organization, filteredUsers); - - if (result.successful.length === 0) { - throw new Error(); - } - - // When feature flag is enabled, show toast instead of dialog - if (this.dataSource.isIncreasedBulkLimitEnabled()) { - const selectedCount = originalInvitedCount; - const invitedCount = filteredUsers.length; - - if (selectedCount > CloudBulkReinviteLimit) { - const excludedCount = selectedCount - CloudBulkReinviteLimit; - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t( - "bulkReinviteLimitedSuccessToast", - CloudBulkReinviteLimit.toLocaleString(), - selectedCount.toLocaleString(), - excludedCount.toLocaleString(), - ), - }); - } else { - this.toastService.showToast({ - variant: "success", - message: - invitedCount === 1 - ? this.i18nService.t("reinviteSuccessToast") - : this.i18nService.t("bulkReinviteSentToast", invitedCount.toString()), - }); - } - } else { - // Feature flag disabled - show legacy dialog - await this.memberDialogManager.openBulkStatusDialog( - users, - filteredUsers, - Promise.resolve(result.successful), - this.i18nService.t("bulkReinviteMessage"), - ); - } - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = undefined; - } - - async bulkConfirm(organization: Organization) { - if (this.actionPromise != null) { - return; - } - - const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); - - await this.memberDialogManager.openBulkConfirmDialog(organization, users); - await this.load(organization); - } - - async bulkEnableSM(organization: Organization) { - const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); - - await this.memberDialogManager.openBulkEnableSecretsManagerDialog(organization, users); - - this.dataSource.uncheckAllUsers(); - await this.load(organization); - } - - openEventsDialog(user: OrganizationUserView, organization: Organization) { - this.memberDialogManager.openEventsDialog(user, organization); - } - - async resetPassword(user: OrganizationUserView, organization: Organization) { - if (!user || !user.email || !user.id) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("orgUserDetailsNotFound"), - }); - this.logService.error("Org user details not found when attempting account recovery"); - - return; - } - - const result = await this.memberDialogManager.openAccountRecoveryDialog(user, organization); - if (result === AccountRecoveryDialogResultType.Ok) { - await this.load(organization); - } - - return; - } - - protected async removeUserConfirmationDialog(user: OrganizationUserView) { - return await this.memberDialogManager.openRemoveUserConfirmationDialog(user); - } - - protected async revokeUserConfirmationDialog(user: OrganizationUserView) { - return await this.memberDialogManager.openRevokeUserConfirmationDialog(user); - } - - async deleteUser(user: OrganizationUserView, organization: Organization) { - const confirmed = await this.memberDialogManager.openDeleteUserConfirmationDialog( - user, - organization, - ); - - if (!confirmed) { - return false; - } - - this.actionPromise = this.memberActionsService.deleteUser(organization, user.id); - try { - const result = await this.actionPromise; - if (!result.success) { - throw new Error(result.error); - } - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t("organizationUserDeleted", this.userNamePipe.transform(user)), - }); - this.dataSource.removeUser(user); - } catch (e) { - this.validationService.showError(e); - } - this.actionPromise = undefined; - } - - get showBulkRestoreUsers(): boolean { - return this.dataSource - .getCheckedUsers() - .every((member) => member.status == this.userStatusType.Revoked); - } - - get showBulkRevokeUsers(): boolean { - return this.dataSource - .getCheckedUsers() - .every((member) => member.status != this.userStatusType.Revoked); - } - - get showBulkRemoveUsers(): boolean { - return this.dataSource.getCheckedUsers().every((member) => !member.managedByOrganization); - } - - get showBulkDeleteUsers(): boolean { - const validStatuses = [ - this.userStatusType.Accepted, - this.userStatusType.Confirmed, - this.userStatusType.Revoked, - ]; - - return this.dataSource - .getCheckedUsers() - .every((member) => member.managedByOrganization && validStatuses.includes(member.status)); - } - - get selectedInvitedCount(): number { - return this.dataSource - .getCheckedUsers() - .filter((member) => member.status === this.userStatusType.Invited).length; - } - - get isSingleInvite(): boolean { - return this.selectedInvitedCount === 1; - } - - exportMembers = () => { - const result = this.memberExportService.getMemberExport(this.dataSource.data); - if (result.success) { - this.toastService.showToast({ - variant: "success", - title: undefined, - message: this.i18nService.t("dataExportSuccess"), - }); - } - - if (result.error != null) { - this.validationService.showError(result.error.message); - } - }; -} diff --git a/apps/web/src/app/admin-console/organizations/members/members-routing.module.ts b/apps/web/src/app/admin-console/organizations/members/members-routing.module.ts index 2f22b9871b7..153a2f3a956 100644 --- a/apps/web/src/app/admin-console/organizations/members/members-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/members/members-routing.module.ts @@ -1,30 +1,23 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { canAccessMembersTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { FreeBitwardenFamiliesComponent } from "../../../billing/members/free-bitwarden-families.component"; import { organizationPermissionsGuard } from "../guards/org-permissions.guard"; import { canAccessSponsoredFamilies } from "./../../../billing/guards/can-access-sponsored-families.guard"; -import { MembersComponent } from "./deprecated_members.component"; -import { vNextMembersComponent } from "./members.component"; +import { MembersComponent } from "./members.component"; const routes: Routes = [ - ...featureFlaggedRoute({ - defaultComponent: MembersComponent, - flaggedComponent: vNextMembersComponent, - featureFlag: FeatureFlag.MembersComponentRefactor, - routeOptions: { - path: "", - canActivate: [organizationPermissionsGuard(canAccessMembersTab)], - data: { - titleId: "members", - }, + { + path: "", + component: MembersComponent, + canActivate: [organizationPermissionsGuard(canAccessMembersTab)], + data: { + titleId: "members", }, - }), + }, { path: "sponsored-families", component: FreeBitwardenFamiliesComponent, diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.spec.ts b/apps/web/src/app/admin-console/organizations/members/members.component.spec.ts index 9a371de1acd..72c12fd4d79 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.spec.ts @@ -36,7 +36,7 @@ import { OrganizationUserView } from "../core/views/organization-user.view"; import { AccountRecoveryDialogResultType } from "./components/account-recovery/account-recovery-dialog.component"; import { MemberDialogResult } from "./components/member-dialog"; -import { vNextMembersComponent } from "./members.component"; +import { MembersComponent } from "./members.component"; import { MemberDialogManagerService, MemberExportService, @@ -48,9 +48,9 @@ import { MemberActionResult, } from "./services/member-actions/member-actions.service"; -describe("vNextMembersComponent", () => { - let component: vNextMembersComponent; - let fixture: ComponentFixture; +describe("MembersComponent", () => { + let component: MembersComponent; + let fixture: ComponentFixture; let mockApiService: MockProxy; let mockI18nService: MockProxy; @@ -172,7 +172,7 @@ describe("vNextMembersComponent", () => { mockFileDownloadService = mock(); await TestBed.configureTestingModule({ - declarations: [vNextMembersComponent], + declarations: [MembersComponent], providers: [ { provide: ApiService, useValue: mockApiService }, { provide: I18nService, useValue: mockI18nService }, @@ -211,13 +211,13 @@ describe("vNextMembersComponent", () => { ], schemas: [NO_ERRORS_SCHEMA], }) - .overrideComponent(vNextMembersComponent, { + .overrideComponent(MembersComponent, { remove: { imports: [] }, add: { template: "
" }, }) .compileComponents(); - fixture = TestBed.createComponent(vNextMembersComponent); + fixture = TestBed.createComponent(MembersComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 826bdfb5f69..6b93edc8c6b 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -82,7 +82,7 @@ interface BulkMemberFlags { templateUrl: "members.component.html", standalone: false, }) -export class vNextMembersComponent { +export class MembersComponent { protected i18nService = inject(I18nService); protected validationService = inject(ValidationService); protected logService = inject(LogService); diff --git a/apps/web/src/app/admin-console/organizations/members/members.module.ts b/apps/web/src/app/admin-console/organizations/members/members.module.ts index 54e2d1b6373..92ae71123cc 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.module.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.module.ts @@ -19,9 +19,8 @@ import { BulkRemoveDialogComponent } from "./components/bulk/bulk-remove-dialog. import { BulkRestoreRevokeComponent } from "./components/bulk/bulk-restore-revoke.component"; import { BulkStatusComponent } from "./components/bulk/bulk-status.component"; import { UserDialogModule } from "./components/member-dialog"; -import { MembersComponent } from "./deprecated_members.component"; import { MembersRoutingModule } from "./members-routing.module"; -import { vNextMembersComponent } from "./members.component"; +import { MembersComponent } from "./members.component"; import { UserStatusPipe } from "./pipes"; import { OrganizationMembersService, @@ -52,7 +51,6 @@ import { BulkProgressDialogComponent, BulkReinviteFailureDialogComponent, MembersComponent, - vNextMembersComponent, BulkDeleteDialogComponent, UserStatusPipe, ], diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/deprecated_members.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/deprecated_members.component.html deleted file mode 100644 index 5478601e72c..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/deprecated_members.component.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - -
- - - {{ "all" | i18n }} - - {{ allCount }} - - - - {{ "invited" | i18n }} - - {{ invitedCount }} - - - - {{ "needsConfirmation" | i18n }} - - {{ acceptedCount }} - - - -
- - - - {{ "loading" | i18n }} - - - -

{{ "noMembersInList" | i18n }}

- - - {{ "providerUsersNeedConfirmed" | i18n }} - - - - - - - - - - {{ "name" | i18n }} - {{ "role" | i18n }} - - - - - - - - - - - - - - - - -
- -
-
- - - {{ "invited" | i18n }} - - - {{ "needsConfirmation" | i18n }} - - - {{ "revoked" | i18n }} - -
-
- {{ user.email }} -
-
-
- - - {{ "providerAdmin" | i18n }} - {{ "serviceUser" | i18n }} - - - - - - - - - - - -
-
-
-
-
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/deprecated_members.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/deprecated_members.component.ts deleted file mode 100644 index 464b9982689..00000000000 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/deprecated_members.component.ts +++ /dev/null @@ -1,351 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute, Router } from "@angular/router"; -import { combineLatest, firstValueFrom, lastValueFrom, switchMap } from "rxjs"; -import { first, map } from "rxjs/operators"; - -import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; -import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; -import { ProviderUserStatusType, ProviderUserType } from "@bitwarden/common/admin-console/enums"; -import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request"; -import { ProviderUserConfirmRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-confirm.request"; -import { ProviderUserUserDetailsResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user.response"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { assertNonNullish } from "@bitwarden/common/auth/utils"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; -import { ProviderId } from "@bitwarden/common/types/guid"; -import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; -import { BaseMembersComponent } from "@bitwarden/web-vault/app/admin-console/common/base-members.component"; -import { - CloudBulkReinviteLimit, - MaxCheckedCount, - peopleFilter, - PeopleTableDataSource, -} from "@bitwarden/web-vault/app/admin-console/common/people-table-data-source"; -import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component"; -import { BulkStatusComponent } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/bulk-status.component"; -import { MemberActionResult } from "@bitwarden/web-vault/app/admin-console/organizations/members/services/member-actions/member-actions.service"; - -import { - AddEditMemberDialogComponent, - AddEditMemberDialogParams, - AddEditMemberDialogResultType, -} from "./dialogs/add-edit-member-dialog.component"; -import { BulkConfirmDialogComponent } from "./dialogs/bulk-confirm-dialog.component"; -import { BulkRemoveDialogComponent } from "./dialogs/bulk-remove-dialog.component"; - -type ProviderUser = ProviderUserUserDetailsResponse; - -class MembersTableDataSource extends PeopleTableDataSource { - protected statusType = ProviderUserStatusType; -} - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - templateUrl: "deprecated_members.component.html", - standalone: false, -}) -export class MembersComponent extends BaseMembersComponent { - accessEvents = false; - dataSource: MembersTableDataSource; - loading = true; - providerId: string; - rowHeight = 70; - rowHeightClass = `tw-h-[70px]`; - status: ProviderUserStatusType = null; - - userStatusType = ProviderUserStatusType; - userType = ProviderUserType; - - constructor( - apiService: ApiService, - keyService: KeyService, - dialogService: DialogService, - i18nService: I18nService, - logService: LogService, - organizationManagementPreferencesService: OrganizationManagementPreferencesService, - toastService: ToastService, - userNamePipe: UserNamePipe, - validationService: ValidationService, - private encryptService: EncryptService, - private activatedRoute: ActivatedRoute, - private providerService: ProviderService, - private router: Router, - private accountService: AccountService, - private environmentService: EnvironmentService, - ) { - super( - apiService, - i18nService, - keyService, - validationService, - logService, - userNamePipe, - dialogService, - organizationManagementPreferencesService, - toastService, - ); - - this.dataSource = new MembersTableDataSource(this.environmentService); - - combineLatest([ - this.activatedRoute.parent.params, - this.activatedRoute.queryParams.pipe(first()), - ]) - .pipe( - switchMap(async ([urlParams, queryParams]) => { - this.searchControl.setValue(queryParams.search); - this.dataSource.filter = peopleFilter(queryParams.search, null); - - this.providerId = urlParams.providerId; - const provider = await firstValueFrom( - this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => this.providerService.get$(this.providerId, userId)), - ), - ); - - if (!provider || !provider.canManageUsers) { - return await this.router.navigate(["../"], { relativeTo: this.activatedRoute }); - } - this.accessEvents = provider.useEvents; - await this.load(); - - if (queryParams.viewEvents != null) { - const user = this.dataSource.data.find((user) => user.id === queryParams.viewEvents); - if (user && user.status === ProviderUserStatusType.Confirmed) { - this.openEventsDialog(user); - } - } - }), - takeUntilDestroyed(), - ) - .subscribe(); - } - - async bulkConfirm(): Promise { - if (this.actionPromise != null) { - return; - } - - const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); - - const dialogRef = BulkConfirmDialogComponent.open(this.dialogService, { - data: { - providerId: this.providerId, - users: users, - }, - }); - - await lastValueFrom(dialogRef.closed); - await this.load(); - } - - async bulkReinvite(): Promise { - if (this.actionPromise != null) { - return; - } - - let users: ProviderUser[]; - if (this.dataSource.isIncreasedBulkLimitEnabled()) { - users = this.dataSource.getCheckedUsersInVisibleOrder(); - } else { - users = this.dataSource.getCheckedUsers(); - } - - const allInvitedUsers = users.filter((user) => user.status === ProviderUserStatusType.Invited); - - // Capture the original count BEFORE enforcing the limit - const originalInvitedCount = allInvitedUsers.length; - - // When feature flag is enabled, limit invited users and uncheck the excess - let checkedInvitedUsers: ProviderUser[]; - if (this.dataSource.isIncreasedBulkLimitEnabled()) { - checkedInvitedUsers = this.dataSource.limitAndUncheckExcess( - allInvitedUsers, - CloudBulkReinviteLimit, - ); - } else { - checkedInvitedUsers = allInvitedUsers; - } - - if (checkedInvitedUsers.length <= 0) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("noSelectedUsersApplicable"), - }); - return; - } - - try { - // When feature flag is enabled, show toast instead of dialog - if (this.dataSource.isIncreasedBulkLimitEnabled()) { - await this.apiService.postManyProviderUserReinvite( - this.providerId, - new ProviderUserBulkRequest(checkedInvitedUsers.map((user) => user.id)), - ); - - const selectedCount = originalInvitedCount; - const invitedCount = checkedInvitedUsers.length; - - if (selectedCount > CloudBulkReinviteLimit) { - const excludedCount = selectedCount - CloudBulkReinviteLimit; - this.toastService.showToast({ - variant: "success", - message: this.i18nService.t( - "bulkReinviteLimitedSuccessToast", - CloudBulkReinviteLimit.toLocaleString(), - selectedCount.toLocaleString(), - excludedCount.toLocaleString(), - ), - }); - } else { - this.toastService.showToast({ - variant: "success", - message: - invitedCount === 1 - ? this.i18nService.t("reinviteSuccessToast") - : this.i18nService.t("bulkReinviteSentToast", invitedCount.toString()), - }); - } - } else { - // Feature flag disabled - show legacy dialog - const request = this.apiService - .postManyProviderUserReinvite( - this.providerId, - new ProviderUserBulkRequest(checkedInvitedUsers.map((user) => user.id)), - ) - .then((response) => response.data); - - const dialogRef = BulkStatusComponent.open(this.dialogService, { - data: { - users: users, - filteredUsers: checkedInvitedUsers, - request, - successfulMessage: this.i18nService.t("bulkReinviteMessage"), - }, - }); - await lastValueFrom(dialogRef.closed); - } - } catch (error) { - this.validationService.showError(error); - } - } - - async invite() { - await this.edit(null); - } - - async bulkRemove(): Promise { - if (this.actionPromise != null) { - return; - } - - const users = this.dataSource.getCheckedUsersWithLimit(MaxCheckedCount); - - const dialogRef = BulkRemoveDialogComponent.open(this.dialogService, { - data: { - providerId: this.providerId, - users: users, - }, - }); - - await lastValueFrom(dialogRef.closed); - await this.load(); - } - - async confirmUser(user: ProviderUser, publicKey: Uint8Array): Promise { - try { - const providerKey = await firstValueFrom( - this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => this.keyService.providerKeys$(userId)), - map((providerKeys) => providerKeys?.[this.providerId as ProviderId] ?? null), - ), - ); - assertNonNullish(providerKey, "Provider key not found"); - - const key = await this.encryptService.encapsulateKeyUnsigned(providerKey, publicKey); - const request = new ProviderUserConfirmRequest(key.encryptedString); - await this.apiService.postProviderUserConfirm(this.providerId, user.id, request); - return { success: true }; - } catch (error) { - return { success: false, error: error.message }; - } - } - - removeUser = async (id: string): Promise => { - try { - await this.apiService.deleteProviderUser(this.providerId, id); - return { success: true }; - } catch (error) { - return { success: false, error: error.message }; - } - }; - - edit = async (user: ProviderUser | null): Promise => { - const data: AddEditMemberDialogParams = { - providerId: this.providerId, - user, - }; - - const dialogRef = AddEditMemberDialogComponent.open(this.dialogService, { - data, - }); - - const result = await lastValueFrom(dialogRef.closed); - - switch (result) { - case AddEditMemberDialogResultType.Saved: - case AddEditMemberDialogResultType.Deleted: - await this.load(); - break; - } - }; - - openEventsDialog = (user: ProviderUser): DialogRef => - openEntityEventsDialog(this.dialogService, { - data: { - name: this.userNamePipe.transform(user), - providerId: this.providerId, - entityId: user.id, - showUser: false, - entity: "user", - }, - }); - - getUsers = (): Promise> => - this.apiService.getProviderUsers(this.providerId); - - reinviteUser = async (id: string): Promise => { - try { - await this.apiService.postProviderUserReinvite(this.providerId, id); - return { success: true }; - } catch (error) { - return { success: false, error: error.message }; - } - }; - - get selectedInvitedCount(): number { - return this.dataSource - .getCheckedUsers() - .filter((member) => member.status === this.userStatusType.Invited).length; - } - - get isSingleInvite(): boolean { - return this.selectedInvitedCount === 1; - } -} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts index 308b93ac2e3..d4a6ba92451 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts @@ -61,7 +61,7 @@ interface BulkProviderFlags { templateUrl: "members.component.html", standalone: false, }) -export class vNextMembersComponent { +export class MembersComponent { protected apiService = inject(ApiService); protected dialogService = inject(DialogService); protected i18nService = inject(I18nService); diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts index 447481a8bcb..5fadc935644 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers-routing.module.ts @@ -2,9 +2,7 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { authGuard } from "@bitwarden/angular/auth/guards"; -import { featureFlaggedRoute } from "@bitwarden/angular/platform/utils/feature-flagged-route"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AnonLayoutWrapperComponent } from "@bitwarden/components"; import { FrontendLayoutComponent } from "@bitwarden/web-vault/app/layouts/frontend-layout.component"; import { UserLayoutComponent } from "@bitwarden/web-vault/app/layouts/user-layout.component"; @@ -17,9 +15,8 @@ import { ProviderSubscriptionComponent } from "../../billing/providers/subscript import { ManageClientsComponent } from "./clients/manage-clients.component"; import { providerPermissionsGuard } from "./guards/provider-permissions.guard"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; -import { MembersComponent } from "./manage/deprecated_members.component"; import { EventsComponent } from "./manage/events.component"; -import { vNextMembersComponent } from "./manage/members.component"; +import { MembersComponent } from "./manage/members.component"; import { ProvidersLayoutComponent } from "./providers-layout.component"; import { ProvidersComponent } from "./providers.component"; import { AccountComponent } from "./settings/account.component"; @@ -95,20 +92,16 @@ const routes: Routes = [ pathMatch: "full", redirectTo: "members", }, - ...featureFlaggedRoute({ - defaultComponent: MembersComponent, - flaggedComponent: vNextMembersComponent, - featureFlag: FeatureFlag.MembersComponentRefactor, - routeOptions: { - path: "members", - canActivate: [ - providerPermissionsGuard((provider: Provider) => provider.canManageUsers), - ], - data: { - titleId: "members", - }, + { + path: "members", + component: MembersComponent, + canActivate: [ + providerPermissionsGuard((provider: Provider) => provider.canManageUsers), + ], + data: { + titleId: "members", }, - }), + }, { path: "events", component: EventsComponent, diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts index 44e2e51637f..abdd35c5e61 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.module.ts @@ -27,12 +27,11 @@ import { CreateClientDialogComponent } from "./clients/create-client-dialog.comp import { ManageClientNameDialogComponent } from "./clients/manage-client-name-dialog.component"; import { ManageClientSubscriptionDialogComponent } from "./clients/manage-client-subscription-dialog.component"; import { AcceptProviderComponent } from "./manage/accept-provider.component"; -import { MembersComponent } from "./manage/deprecated_members.component"; import { AddEditMemberDialogComponent } from "./manage/dialogs/add-edit-member-dialog.component"; import { BulkConfirmDialogComponent } from "./manage/dialogs/bulk-confirm-dialog.component"; import { BulkRemoveDialogComponent } from "./manage/dialogs/bulk-remove-dialog.component"; import { EventsComponent } from "./manage/events.component"; -import { vNextMembersComponent } from "./manage/members.component"; +import { MembersComponent } from "./manage/members.component"; import { ProviderActionsService } from "./manage/services/provider-actions/provider-actions.service"; import { ProvidersLayoutComponent } from "./providers-layout.component"; import { ProvidersRoutingModule } from "./providers-routing.module"; @@ -67,7 +66,6 @@ import { VerifyRecoverDeleteProviderComponent } from "./verify-recover-delete-pr BulkConfirmDialogComponent, BulkRemoveDialogComponent, EventsComponent, - vNextMembersComponent, MembersComponent, SetupComponent, SetupProviderComponent, diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts index 65a31896341..2307eab04fe 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts @@ -1,8 +1,10 @@ import { BehaviorSubject, combineLatest, Observable } from "rxjs"; import { map, shareReplay } from "rxjs/operators"; -import { RiskInsightsDataService } from "@bitwarden/bit-common/dirt/reports/risk-insights"; -import { SecurityTasksApiService } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { + RiskInsightsDataService, + SecurityTasksApiService, +} from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; import { SecurityTask, SecurityTaskStatus, SecurityTaskType } from "@bitwarden/common/vault/tasks"; diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 4db9ff37d42..05fded6bcaf 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -13,7 +13,6 @@ export enum FeatureFlag { /* Admin Console Team */ AutoConfirm = "pm-19934-auto-confirm-organization-users", DefaultUserCollectionRestore = "pm-30883-my-items-restored-users", - MembersComponentRefactor = "pm-29503-refactor-members-inheritance", BulkReinviteUI = "pm-28416-bulk-reinvite-ux-improvements", /* Auth */ @@ -109,7 +108,6 @@ export const DefaultFeatureFlagValue = { /* Admin Console Team */ [FeatureFlag.AutoConfirm]: FALSE, [FeatureFlag.DefaultUserCollectionRestore]: FALSE, - [FeatureFlag.MembersComponentRefactor]: FALSE, [FeatureFlag.BulkReinviteUI]: FALSE, /* Autofill */