import { Component, OnInit, OnDestroy, ViewChild, Inject, ViewEncapsulation, NgZone } from '@angular/core'; import { ITeamMember } from './team-member.interface'; import { SocketsService, Utils, AnalyticsService, DialogService, AnalyticsEvent, UserService, User, ApiSocketEmitAction, ApiSocketListenAction } from '@arrow/bom/core'; import { WPT_DIALOG_DATA, WptDialogRef } from '@arrow/warpaint/dialog'; import { Observable, Subject } from 'rxjs'; import { FormControl } from '@angular/forms'; import { startWith, map, takeUntil } from 'rxjs/operators'; import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { ConfigService } from '@arrow/bom/config'; import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'bom-share', templateUrl: 'share-bom.component.html', styleUrls: [ '../../../core/src/style/modal.scss', './share-bom.component.scss' ], encapsulation: ViewEncapsulation.None }) export class ShareBomComponent implements OnInit, OnDestroy { // Kill signal for observables private _killSig$$ = new Subject(); @ViewChild('addEmailInput') addEmailInput: any; @ViewChild('smartSearch') smartSearchInput: any; @ViewChild(MatAutocompleteTrigger) autocompleteTrigger: MatAutocompleteTrigger; MAX_RECIPIENTS: number = 50; DEFAULT_EMAIL: string = null; DEFAULT_PERMISSION: string = 'r'; MY_ARROW = 'myarrow'; subscriptions: Array = []; isShowModal: boolean = true; // A list that is updated through the UI (delete/modify permissions) // and then compared to the original to decide which users got added, removed or altered pendingTeamList: ITeamMember[] = []; // The "freshest" team list received over sockets // (should be constant and unchanged) originalTeamList: ITeamMember[] = []; newAddEmail: string = null; newAddPermission: string = this.DEFAULT_PERMISSION; userCollegueList: User[] = []; $filterResults: Observable; emailAutocompleteControl = new FormControl(); /** * Does the "add new" email input (bottom-most one) have a valid input? */ isNewAddValid: boolean = true; /** * Tooltip message to display for the new add field when it's invalid */ newAddInvalidMessage: string = null; /** * Keeps track of how many "net users" there will be after a submit * example: if there are already 5 members in a team, and the user adds 3 news ones * and deletes 1, then the net will be 7. * * This is used to prevent going over the MAX_RECIPIENTS value and also to allow the user * to add more members while deleting others * (otherwise we'll have the issue of not being able to add new users until we have submitted * others to be deleted first) * Also used for Google Analytics */ netUsersCount: number = 0; /** * Keeps track of whether there are unsubmitted changes in the team list * In some cases we can set this as true immediately after a change (like a new user added) * In other cases we have to run through the entire list to see if they might no longer * be any unsaved changes (deleting a newly added but unsubmitted user) * The 'this.refreshUnsavedChanges()' function takes care of that */ unsavedChanges: boolean = false; /** * Are all "pending add" emails valid? (ones that have been added to the list, but not yet submitted) * this does NOT include the bottom-most input field */ isAllPendingValid: boolean = true; // keeps track of how many sockets 'action result' messages are remaining to come back // when sending out edits, this number increments by 1 of each edit // when responses received, we subtract 1 for each response numResponsesRemain: number = 0; // if this is true and number of responses remaining is 0, then close the modal isReadyToClose: boolean = false; isSubmitButtonClicked: boolean = false; isLoading: boolean = true; isMyArrow: boolean = false; constructor( @Inject(WPT_DIALOG_DATA) public data: any, public dialogRef: WptDialogRef, private _sockets: SocketsService, private _dialog: DialogService, private _analyticsService: AnalyticsService, private _utils: Utils, private _userService: UserService, private _config: ConfigService, private _translate: TranslateService, private _zone: NgZone ) {} ngOnInit() { this.isMyArrow = this._config.getTarget() === this.MY_ARROW; // This sets up a listener for incoming "team list" sockets messages this._sockets .observeAction(ApiSocketListenAction.TEAM_LIST) .pipe(takeUntil(this._killSig$$)) .subscribe((data: ITeamMember[]) => { // This sends the team list data to a method, so // it can process it and update the view this._initTeamMembers(data, this.MAX_RECIPIENTS); }); this._sockets .observeAction(ApiSocketListenAction.ACTION_RESULT) .pipe(takeUntil(this._killSig$$)) .subscribe((data: any) => { this._zone.run(() => { const action = data.action.toLowerCase(); if ( 'addshare' === action || 'altershare' === action || 'removeshare' === action ) { this.handleResponseCountsAndClose(this.isReadyToClose, this.data); } }) }); // for now, if the BE returns an API error as a result of one of the team methods // just close the modal so that the error modal that pops up, can be seen by the user // otherwise the "Share Modal" will be stuck loading, while the error modal is popped "under" it this._sockets .observeApiErrors() .pipe(takeUntil(this._killSig$$)) .subscribe((errorData: any) => { if ( errorData.action === 'AddShare' || errorData.action === 'AlterShare' || errorData.action === 'RemoveShare' || errorData.action === 'getShares' ) { // We get the team list just in case SOME edits WERE successful // or in the case of 'getShares' the current user is no longer // authenticated (or some other API exception happened) this._requestTeamList(); this.closeModal(); } }); if (this.isMyArrow) { this._userService.getUserCollegues().subscribe(response => { const jsonResponse = JSON.stringify(response); this.userCollegueList = JSON.parse(jsonResponse); this.autocompleteTrigger.closePanel(); if (!this.userCollegueList.length) { this._disableAutoComplete(); } else { this._filterResults( this.netUsersCount, this.MAX_RECIPIENTS, this.userCollegueList ); } this.isLoading = false; }); } else { this.isLoading = false; } this._requestTeamList(); } ngOnDestroy(): void { this._killSig$$.next(); this._killSig$$.complete(); } private _disableAutoComplete() { this.emailAutocompleteControl.setValue(''); this.emailAutocompleteControl.disable(); } /** * @param netUsersCount * @param MAX_RECIPIENTS * @param userCollegueList */ private _filterResults( netUsersCount: number, MAX_RECIPIENTS: number, userCollegueList: User[] ) { if (netUsersCount <= MAX_RECIPIENTS) { this.$filterResults = this.emailAutocompleteControl.valueChanges.pipe( startWith(''), map(value => this._filter(value, userCollegueList)) ); } } private _filter(value: string, userCollegueList: User[]): User[] { if (value && typeof value === 'string') { return userCollegueList.filter( op => op.firstName.toLowerCase().includes(value.toLowerCase()) || op.lastName.toLowerCase().includes(value.toLowerCase()) || op.email.toLowerCase().includes(value.toLowerCase()) ); } return userCollegueList; } /** * Init team members to share bom * @param teamList * @param pendingTeamList * @param originalTeamList * @param MAX_RECIPIENTS */ private _initTeamMembers( teamList: ITeamMember[], MAX_RECIPIENTS: number ): void { // In a rare case that we have a second incoming team list, leave the UI alone // TODO: Come up with a better way to handle multiple incoming teamLists if (this.pendingTeamList.length === 0) { this.pendingTeamList = teamList; this.pendingTeamList.forEach(user => { if (user.permission === 'OWN') user.isCurrentOwner = true; }); this.originalTeamList = this.cloneTeamList(teamList); this.netUsersCount = this.originalTeamList.length; } } // We make a request for the team list as soon as the dialog opens // TODO: have this call be done before the dialog even opens private _requestTeamList() { this._sockets.emitAction('getTeam', { bomId: this.data.bom.id }); } /** * clear input * @param input */ cleanInput(input: any) { input.value = ''; input.blur(); this.autocompleteTrigger.closePanel(); } /** * handle changes * @param event * @param pendingTeamList * @param smartSearchInput * @param originalTeamList * @param MAX_RECIPIENTS * @param DEFAULT_EMAIL * @param DEFAULT_PERMISSION */ onSelectionChanged( event: MatAutocompleteSelectedEvent, pendingTeamList: ITeamMember[], smartSearchInput: any, originalTeamList: ITeamMember[], MAX_RECIPIENTS: number, DEFAULT_EMAIL: string, DEFAULT_PERMISSION: string ) { const email = event.option.value.email; const exists = pendingTeamList .map(function(e) { return e.email; }) .indexOf(email); if (exists === -1) { this.userCollegueList.splice(exists); this.addNewUser( email, this.newAddPermission, pendingTeamList, originalTeamList, MAX_RECIPIENTS, DEFAULT_EMAIL, DEFAULT_PERMISSION ); } this.cleanInput(smartSearchInput.nativeElement); } /** * Handles a request to delete a user * Scenario 1: A user that was already in the list when the modal was opened is deleted * Scenario 2: A user that was just added to the list, is being deleted * @param userId * @param email * @param pendingTeamList * @param originalTeamList */ deleteUser( userId: string, email: string, pendingTeamList: ITeamMember[], originalTeamList: ITeamMember[] ): void { if (userId === null) { // If user ID is null then user is removing an "unsubmitted" email, and don't 'grey' it out // since this sharee hasn't actually been submitted to the BE, removing it from the pending list // results in "no chnages" for this user const matchedUserIndex = pendingTeamList.findIndex( user => user.email === email ); pendingTeamList.splice(matchedUserIndex, 1); this.refreshUnsavedChanges(pendingTeamList); this.refreshInvalidState(pendingTeamList); } else { // User ID exists - remove from pending list via user ID const matchedUser = pendingTeamList.find(user => user.userid === userId); matchedUser.isDelete = true; this.unsavedChanges = true; } this.refreshNetUserCount(pendingTeamList, originalTeamList); } /** * Triggered when the "reverse arrow" is clicked on a deleted user * @param userId * @param pendingTeamList * @param originalTeamList */ undoDeleteUser( userId: string, pendingTeamList: ITeamMember[], originalTeamList: ITeamMember[] ): void { const userUndelete = pendingTeamList.find(user => user.userid === userId); // IF this user that is being "undeleted" had a PENDING permissions change set to 'OWN' // THEN check if another user got set to 'OWN' while this one was pending deletion if ( userUndelete.isPendingPermissionChange && userUndelete.permission === 'OWN' ) { const isPendingOwner = !!this.getPendingOwner(); // A new owner was set while this one was "deleted" // change back this 'undeleted' users permissions to 'rw' if (isPendingOwner) { const originalPerms = originalTeamList.find( user => user.userid === userId ).permission; userUndelete.permission = 'rw'; if (originalPerms === 'rw') userUndelete.isPendingPermissionChange = false; } } userUndelete.isDelete = false; this.refreshUnsavedChanges(pendingTeamList); this.refreshNetUserCount(pendingTeamList, originalTeamList); } /** * This method handles marking a user as having unsaved changes or not, handling * setting someone as owner and deciding whether to refresh the "unsaved changes" status * This does not actually set permissions since [(ngModel)] is already used, so by * the time we're in this method, the permission has already been set * @param userId * @param setPermission * @param pendingTeamList * @param originalTeamList */ handleChangePermission( userId: string, setPermission: string, pendingTeamList: ITeamMember[], originalTeamList: ITeamMember[] ): void { if (userId !== null) { const originalUser = originalTeamList.find( user => user.userid === userId ); const pendingUser = pendingTeamList.find(user => user.userid === userId); if (originalUser.permission !== setPermission) { if (setPermission === 'OWN') { // Try to the user who alreay has a pending 'OWN' permission const previousPendingOwner = this.getPendingOwnerWithOmit(userId); // If user with pending 'OWN' permission already exists, // revert that users permission to 'rw' if (previousPendingOwner) { // Pull out this users original user object to check original permissions const originalPendingOwner = originalTeamList.find( user => user.userid === previousPendingOwner.userid ); // When switching the pending owner, give the previous pending owner 'rw' permisions previousPendingOwner.permission = 'rw'; // If before this user was given 'OWN' permissions, // they had 'rw', then now they no longer have pending // permissions (nothing is changing for them) if (originalPendingOwner.permission === 'rw') { previousPendingOwner.isPendingPermissionChange = false; } } else { pendingUser.isPendingPermissionChange = true; } } pendingUser.isPendingPermissionChange = true; this.unsavedChanges = true; } else { pendingUser.isPendingPermissionChange = false; this.refreshUnsavedChanges(pendingTeamList); } } else { // we don't need to worry about unsaved changes for unsubmitted (pending add) users // since they have already have a 'pendingAdd' boolean = true } } /** * add new user to list of users recipients * @param newAddEmail * @param newAddPermission * @param pendingTeamList * @param originalTeamList * @param MAX_RECIPIENTS * @param DEFAULT_EMAIL * @param DEFAULT_PERMISSION */ addNewUser( newAddEmail: string, newAddPermission: string, pendingTeamList: ITeamMember[], originalTeamList: ITeamMember[], MAX_RECIPIENTS: number, DEFAULT_EMAIL: string, DEFAULT_PERMISSION: string ): void { // Prevent submitting empty entries if (this._utils.isNullOrWhitespace(newAddEmail)) { this.isNewAddValid = true; this.newAddInvalidMessage = null; this.refreshInvalidState(pendingTeamList); return; } newAddEmail = newAddEmail.trim().toLowerCase(); // Ignore duplicates, owner or invalid const isValid = this._utils.isValidEmail(newAddEmail); const isDuplicate = this.isDuplicateEmail(newAddEmail); const isOwner = this.isOwnerEmail(newAddEmail); if (!isValid) { this.isNewAddValid = false; this.newAddInvalidMessage = this._translate.instant( 'Email-format-is-invalid' ); } else if (isOwner) { this.isNewAddValid = false; this.newAddInvalidMessage = this._translate.instant('BOM-owner-email'); } else if (isDuplicate) { this.isNewAddValid = false; this.newAddInvalidMessage = this._translate.instant( 'Email-already-exists' ); } else { this.isNewAddValid = true; this.newAddInvalidMessage = null; } this.refreshInvalidState(pendingTeamList); if (!this.isNewAddValid) return; const matchedOriginalUserIndex = originalTeamList.findIndex( user => user.email === this.newAddEmail ); if (matchedOriginalUserIndex > -1) { // User is trying to re-add an original user that they removed but hadn't yet submitted // Let's grab that original userID, and re-add the user to the UI // If current pending permission is different from the original - update it const originalMember: ITeamMember = { email: originalTeamList[matchedOriginalUserIndex].email, permission: newAddPermission, userid: originalTeamList[matchedOriginalUserIndex].userid }; pendingTeamList.unshift(originalMember); } else { // Add new member to the pending team list const newMember: ITeamMember = { email: newAddEmail, permission: newAddPermission, userid: null, isPendingAdd: true }; pendingTeamList.unshift(newMember); this.unsavedChanges = true; } // Reset the model to prepare for the next input this.newAddEmail = DEFAULT_EMAIL; this.newAddPermission = DEFAULT_PERMISSION; // Don't try to focus on the input when it will be hidden if (this.netUsersCount <= MAX_RECIPIENTS && this.addEmailInput) this.addEmailInput.nativeElement.focus(); this.refreshNetUserCount(pendingTeamList, originalTeamList); } /** * @param pendingTeamList * @param netUsersCount * @param data */ submitTeamUpdates( pendingTeamList: ITeamMember[], netUsersCount: number, data: any ): void { // Check for deletes const membersToDelete: string[] = []; pendingTeamList.forEach(pMember => { if (pMember.isDelete) membersToDelete.push(pMember.userid); }); // Check for adds const membersToAdd: ITeamMember[] = []; pendingTeamList.forEach(pMember => { if (pMember.isPendingAdd) membersToAdd.push(pMember); }); // Check for alter's const membersToAlter: ITeamMember[] = []; pendingTeamList.forEach(pMember => { if (pMember.isPendingPermissionChange && !pMember.isDelete) { membersToAlter.push(pMember); } }); this.numResponsesRemain = membersToAdd.length + membersToAlter.length + membersToDelete.length; if (this.numResponsesRemain > 0) this.isReadyToClose = true; else this.closeModal(); // // GA - don't count owner as one of the users (- 1) this._analyticsService.googleTrackEvent(AnalyticsEvent.CLICK, { category: 'BOM', action: 'Share', label: netUsersCount - 1 }); this.broadcastTeamEditsAndClose( membersToDelete, membersToAdd, membersToAlter, data ); } /** * * @param delTeam * @param addTeam * @param alterTeam * @param data */ broadcastTeamEditsAndClose( delTeam: string[], addTeam: ITeamMember[], alterTeam: ITeamMember[], data: any ) { const totalEdits = delTeam.length + addTeam.length + alterTeam.length; let hasOwnerTransfer = false; delTeam.forEach((userId: string) => this._sockets.emitAction('removeTeam', { bomId: data.bom.id, userGuid: userId }) ); addTeam.forEach(m => this._sockets.emitAction('addTeam', { bomId: data.bom.id, userEmail: m.email, permissionsLevel: m.permission }) ); // Submit all alters EXCEPT for ownership change // Owner will be transferred after all these change // requests have returned a sockets response alterTeam.forEach(m => { if (m.permission !== 'OWN') { this._sockets.emitAction('alterTeam', { bomId: data.bom.id, userGuid: m.userid, permissionsLevel: m.permission }); } else { hasOwnerTransfer = true; } }); // If the only change is an owner transfer, we need to make the request here manually // Since there will not be any countdown happening to make the owner transfer // happen last, we need to request it right away if (totalEdits === 1 && hasOwnerTransfer) { this.makeOwnerTransferRequest(data); } } /** * clone list * @param teamList */ cloneTeamList(teamList: ITeamMember[]): ITeamMember[] { const clonedTeamList: ITeamMember[] = []; for (const member of teamList) { clonedTeamList.push({ userid: member.userid, email: member.email, permission: member.permission }); } return clonedTeamList; } /** * reduces the number of expected responses by 1 and checks if it's time to close the modal * once all responses received, a request for getTeam will be made and the modal closed * If there is a pending new owner, we make sure that the alter request goes out as * the VERY last request (because if we change the owner, and then try to edit a user for that BOM * that will not be allowed) * @param isReadyToClose * @param data */ handleResponseCountsAndClose(isReadyToClose: boolean, data: any): void { const pendingOwner = this.getPendingOwner(); const isPendingOwner = !!pendingOwner; this.numResponsesRemain--; if (isReadyToClose && this.numResponsesRemain < 1) { // Did ownership just get transfered to another user? // if yes, then get permissions again (so the view will auto-update // enabled/disabled state elements) // if not, then refresh the team list if (isPendingOwner) { // triggger get perms so that 'permissions received' handler can update the UI and disable some buttons this._sockets.emitAction(ApiSocketEmitAction.GET_PERMISSIONS, { bomId: data.bom.id }); // update the BOM model, owner so that the "owner email" below the BOM title // will be updated data.bom.owner = pendingOwner.email; } else { this._requestTeamList(); } this.closeModal(); return; } if (isReadyToClose && this.numResponsesRemain === 1 && isPendingOwner) { this.makeOwnerTransferRequest(data); } } /** * Detects unsaved pending changes that haven't been submitted * @param pendingTeamList */ refreshUnsavedChanges(pendingTeamList: ITeamMember[]): void { let isAnyUnsavedChanges = false; pendingTeamList.forEach((pMember: ITeamMember) => { if ( pMember.isDelete || pMember.isPendingAdd || pMember.isPendingPermissionChange ) isAnyUnsavedChanges = true; }); this.unsavedChanges = isAnyUnsavedChanges; } /** * refresh list of user * @param pendingTeamList * @param originalTeamList */ refreshNetUserCount( pendingTeamList: ITeamMember[], originalTeamList: ITeamMember[] ): void { const originalCount = originalTeamList.length; let pendingAdd: number = 0; let pendingDelete: number = 0; pendingTeamList.forEach(pMember => { if (pMember.isDelete) pendingDelete++; else if (pMember.isPendingAdd) pendingAdd++; }); this.netUsersCount = originalCount + pendingAdd - pendingDelete; } /** * handle invalid emails * @param pendingTeamList */ refreshInvalidState(pendingTeamList: ITeamMember[]): void { let isAnyInvalid: boolean = false; pendingTeamList.forEach(pMember => { if (pMember.isInvalidEmail) isAnyInvalid = true; }); this.isAllPendingValid = !isAnyInvalid && this.isNewAddValid; } /** * expects a trimmed email * returns true if email is the same as the user with 'own' permissions * @param val email to check */ isOwnerEmail(val: string): boolean { const matchedOwnerIndex = this.originalTeamList.findIndex(user => { return user.permission.toUpperCase() === 'OWN' && user.email === val; }); const isOwner = matchedOwnerIndex > -1 ? true : false; return isOwner; } /** * expects a trimmed email * returns true if this email already exists in the pending team list * @param val email to check */ isDuplicateEmail(val: string): boolean { let numEmails: number = 0; this.pendingTeamList.forEach(user => { if (user.email === val) { numEmails++; } }); const isDuplicate = numEmails > 0; return isDuplicate; } /** * Triggered when a change is made to an email that is pending to be added * @param user * @param event * @param pendingTeamList */ revalidatePending( event: any, user: ITeamMember, pendingTeamList: ITeamMember[] ): void { let changedEmail: string = event.target.value; // Prevent changing 'pending add email' to an empty field // if the user wants to delete a pending email // they should use the 'X' button if (this._utils.isNullOrWhitespace(changedEmail)) { event.target.value = user.email; return; } changedEmail = changedEmail.trim().toLowerCase(); // If the email hasn't changed, don't compare if (changedEmail === user.email) { // We still need to make sure we reset the valid state of the email // Othwerwise an edge case will occur // (e.g. valid email -> // invalid email -> // back to 1st valid email -> // invalid state NOT cleared) user.isInvalidEmail = false; user.invalidEmailMessage = null; this.refreshInvalidState(pendingTeamList); return; } const isOwnerEmail = this.isOwnerEmail(changedEmail); const isDuplicateEmail = this.isDuplicateEmail(changedEmail); const isValidEmail = this._utils.isValidEmail(changedEmail); if (!isValidEmail) { user.isInvalidEmail = true; user.invalidEmailMessage = this._translate.instant( 'Email-format-is-invalid' ); } else if (isOwnerEmail) { user.isInvalidEmail = true; user.invalidEmailMessage = this._translate.instant('BOM-owner-email'); } else if (isDuplicateEmail) { user.isInvalidEmail = true; user.invalidEmailMessage = this._translate.instant( 'Email-already-exists' ); } else { user.isInvalidEmail = false; user.invalidEmailMessage = null; user.email = changedEmail; } this.refreshInvalidState(pendingTeamList); } focusOnNewAdd(): void { // If input was invalid, mark it valid while the user is typing // on 'blur' the input will be re-checked this.isNewAddValid = true; this.newAddInvalidMessage = null; } /** * handle submit button * @param pendingTeamList * @param netUsersCount * @param data */ onSubmitClick( pendingTeamList: ITeamMember[], netUsersCount: number, data: any ): void { if (!!this.getPendingOwner()) { this.openOwnerTransferModal(pendingTeamList, netUsersCount, data); } else { if (!this.isSubmitButtonClicked) this.isSubmitButtonClicked = true; this.submitTeamUpdates(pendingTeamList, netUsersCount, data); } } /** * open owner transfer modal * @param pendingTeamList * @param netUsersCount * @param data */ openOwnerTransferModal( pendingTeamList: ITeamMember[], netUsersCount: number, data: any ): void { this.hideShareBomModal(); this._dialog .confirm( this._translate.instant('Are-you-sure?'), this._translate.instant( 'You-will-become-an-editor-of-this-BOM-with-limited-permissions.' ) + ' ' + this._translate.instant('This-change-cannot-be-undone.') + ' ' + this._translate.instant( 'Are-you-sure-you-want-to-transfer-ownership?' ), { buttons: { cancel: 'Cancel', confirm: 'Yes' } } ) .subscribe((response: boolean) => { if (response) { if (!this.isSubmitButtonClicked) this.isSubmitButtonClicked = true; this.submitTeamUpdates(pendingTeamList, netUsersCount, data); this.showShareBomModal(); } else { this.showShareBomModal(); } }); } getPendingOwner(): ITeamMember { return this.pendingTeamList.find( user => user.isPendingPermissionChange && !user.isDelete && user.permission === 'OWN' && !user.isCurrentOwner ); } getPendingOwnerWithOmit(omitUserId: string): ITeamMember { return this.pendingTeamList.find( user => user.isPendingPermissionChange && !user.isDelete && user.permission === 'OWN' && !user.isCurrentOwner && user.userid !== omitUserId ); } /** * handle owner transfer request * @param data */ makeOwnerTransferRequest(data: any): void { const newOwner = this.getPendingOwner(); if (newOwner) { this._sockets.emitAction('alterTeam', { bomId: data.bom.id, userGuid: newOwner.userid, permissionsLevel: 'OWN' }); } else { //console.warn('makeOwnerTransferRequest() method called, but "pending new owner" was not found'); } } showShareBomModal(): void { this.isShowModal = true; } hideShareBomModal(): void { this.isShowModal = false; } closeModal(): void { this.dialogRef.close(); } }