Skip to content

Commit

Permalink
[#11878] Add Edit and Approve Account Requests functionality (#12975)
Browse files Browse the repository at this point in the history
* add edit and approve functionality

* remove rejection code

* fix snap

* integrate endpoint

* disable approve button for approved requests

* use comments instead of comment

* use searchString instead of searchQuery

* fix snap
  • Loading branch information
domoberzin authored Apr 7, 2024
1 parent 4a54001 commit 62750b0
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { AccountRequestStatus } from 'src/web/types/api-output';

/**
* Model for the row entries in the account requests table.
*/
export interface AccountRequestTableRowModel {
id: string;
name: string;
email: string;
status: string;
status: AccountRequestStatus;
instituteAndCountry: string;
createdAtText: string;
registeredAtText: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
</td>
<td class="align-middle">
<div class="d-flex flex-row align-items-center justify-content-center gap-2">
<div>
<a id="edit-account-request-{{i}}" href="javascript:;" (click)="$event.stopPropagation(); editAccountRequest(accountRequest);">
<i class="fa-solid fa-pen"></i>
</a>
</div>
<div class="ngb-tooltip-class" [ngbTooltip]="accountRequest.registeredAtText && 'Account requests of registered instructors cannot be deleted'">
<a id="delete-account-request-{{i}}" href="javascript:;" (click)="$event.stopPropagation(); deleteAccountRequest(accountRequest)">
<i class="fa-solid fa-trash"></i>
Expand All @@ -64,7 +69,8 @@
<div *ngIf="searchString" class="ngb-tooltip-class" [ngbTooltip]="accountRequest.registeredAtText && 'Account requests of registered instructors cannot be deleted'">
<button id="reset-account-request-{{i}}" class="btn btn-primary" [disabled]="!accountRequest.registeredAtText" (click)="$event.stopPropagation(); resetAccountRequest(accountRequest);">Reset</button>
</div>
</div>
<button id="approve-account-request-{{i}}" class="btn btn-success" [disabled]="accountRequest.registeredAtText || accountRequest.status === 'APPROVED'" (click)="$event.stopPropagation(); approveAccountRequest(accountRequest)">Approve</button>
</div>
</td>
</tr>
<tr *ngIf="accountRequest.showLinks && searchString">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Component, Input } from '@angular/core';
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { NgbModalRef, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { AccountRequestTableRowModel } from './account-request-table-model';
import { EditRequestModalComponent } from './admin-edit-request-modal/admin-edit-request-modal.component';
import { AccountService } from '../../../services/account.service';
import { SimpleModalService } from '../../../services/simple-modal.service';
import { StatusMessageService } from '../../../services/status-message.service';
import { MessageOutput } from '../../../types/api-output';
import { AccountRequest, AccountRequestStatus, MessageOutput } from '../../../types/api-output';
import { ErrorMessageOutput } from '../../error-message-output';
import { SimpleModalType } from '../simple-modal/simple-modal-type';
import { collapseAnim } from '../teammates-common/collapse-anim';
Expand All @@ -31,6 +32,7 @@ export class AccountRequestTableComponent {
private statusMessageService: StatusMessageService,
private simpleModalService: SimpleModalService,
private accountService: AccountService,
private ngbModal: NgbModal,
) {}

/**
Expand All @@ -51,6 +53,48 @@ export class AccountRequestTableComponent {
}
}

editAccountRequest(accountRequest: AccountRequestTableRowModel): void {
const modalRef: NgbModalRef = this.ngbModal.open(EditRequestModalComponent);
modalRef.componentInstance.accountRequestName = accountRequest.name;
modalRef.componentInstance.accountRequestEmail = accountRequest.email;
modalRef.componentInstance.accountRequestInstitution = accountRequest.instituteAndCountry;
modalRef.componentInstance.accountRequestComments = accountRequest.comments;

modalRef.result.then(() => {
this.accountService.editAccountRequest(
accountRequest.id,
modalRef.componentInstance.accountRequestName,
modalRef.componentInstance.accountRequestEmail,
modalRef.componentInstance.accountRequestInstitution,
accountRequest.status,
modalRef.componentInstance.accountRequestComments)
.subscribe({
next: (resp: AccountRequest) => {
accountRequest.comments = resp.comments ?? '';
accountRequest.name = resp.name;
accountRequest.email = resp.email;
accountRequest.instituteAndCountry = resp.institute;
},
error: (resp: ErrorMessageOutput) => {
this.statusMessageService.showErrorToast(resp.error.message);
},
});
});
}

approveAccountRequest(accountRequest: AccountRequestTableRowModel): void {
this.accountService.approveAccountRequest(accountRequest.id, accountRequest.name,
accountRequest.email, accountRequest.instituteAndCountry)
.subscribe({
next: () => {
accountRequest.status = AccountRequestStatus.APPROVED;
},
error: (resp: ErrorMessageOutput) => {
this.statusMessageService.showErrorToast(resp.error.message);
},
});
}

resetAccountRequest(accountRequest: AccountRequestTableRowModel): void {
const modalContent = `Are you sure you want to reset the account request for
<strong>${accountRequest.name}</strong> with email <strong>${accountRequest.email}</strong> from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgbTooltipModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { AccountRequestTableComponent } from './account-request-table.component';
import { EditRequestModalComponent } from './admin-edit-request-modal/admin-edit-request-modal.component';
import { Pipes } from '../../pipes/pipes.module';
import { RichTextEditorModule } from '../rich-text-editor/rich-text-editor.module';

/**
* Module for account requests table.
*/
@NgModule({
declarations: [
AccountRequestTableComponent,
EditRequestModalComponent,
],
exports: [
AccountRequestTableComponent,
Expand All @@ -21,6 +24,7 @@ import { Pipes } from '../../pipes/pipes.module';
NgbTooltipModule,
NgbDropdownModule,
Pipes,
RichTextEditorModule,
],
})
export class AccountRequestTableModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Result of {@link EditRequestModalComponent}

Check warning on line 2 in src/web/app/components/account-requests-table/admin-edit-request-modal/admin-edit-request-modal-model.ts

View workflow job for this annotation

GitHub Actions / lint (ubuntu-latest)

The type 'EditRequestModalComponent' is undefined

Check warning on line 2 in src/web/app/components/account-requests-table/admin-edit-request-modal/admin-edit-request-modal-model.ts

View workflow job for this annotation

GitHub Actions / lint (windows-latest)

The type 'EditRequestModalComponent' is undefined
*/
export interface EditRequestModalComponentResult {
accountRequestName: string;
accountRequestEmail: string;
accountRequestInstitution: string;
accountRequestComment: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">
<div>Edit Account Request for {{ accountRequestName }}</div>
</h5>
<button type="button" class="btn-close" (click)="activeModal.dismiss()"></button>
</div>
<div id="reject-account-request-modal" class="modal-body">
<div>
<label><b>Name:</b></label>
<input id="request-name" type="text" class="form-control" value="{{ accountRequestName }}" (input)="accountRequestName = $event.target.value">
</div>
<div>
<label><b>Email:</b></label>
<input id="request-email" type="text" class="form-control" value="{{ accountRequestEmail }}" (input)="accountRequestEmail = $event.target.value">
</div>
<div>
<label><b>Institution, Country:</b></label>
<input id="request-institution" type="text" class="form-control" value="{{ accountRequestInstitution }}" (input)="accountRequestInstitution = $event.target.value">
</div>
<div class="row">
<div class="col-12">
<div>
<label><b>Comments:</b></label>
</div>
<div class="form-group">
<textarea id="request-comments" class="form-control" rows="5" value="{{ accountRequestComments }}" (input)="accountRequestComments = $event.target.value"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" (click)="activeModal.dismiss()">Cancel</button>
<button id="btn-confirm-reject-request" type="button" class="btn btn-primary" (click)="edit()">
Save
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Component, Input } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { EditRequestModalComponentResult } from './admin-edit-request-modal-model';

/**
* Modal to select reject account requests with reason.
*/
@Component({
selector: 'tm-edit-request-modal',
templateUrl: './admin-edit-request-modal.component.html',
styleUrls: ['./admin-edit-request-modal.component.scss'],
})

export class EditRequestModalComponent {

@Input()
accountRequestName: string = '';
@Input()
accountRequestEmail: string = '';
@Input()
accountRequestInstitution: string = '';
@Input()
accountRequestComments: string = '';

constructor(public activeModal: NgbActiveModal) {}

/**
* Fires the edit event.
*/
edit(): void {
const result: EditRequestModalComponentResult = {
accountRequestName: this.accountRequestName,
accountRequestEmail: this.accountRequestEmail,
accountRequestInstitution: this.accountRequestInstitution,
accountRequestComment: this.accountRequestComments,
};

this.activeModal.close(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,16 @@ exports[`AdminSearchPageComponent should snap with an expanded account requests
<div
class="d-flex flex-row align-items-center justify-content-center gap-2"
>
<div>
<a
href="javascript:;"
id="edit-account-request-0"
>
<i
class="fa-solid fa-pen"
/>
</a>
</div>
<div
class="ngb-tooltip-class"
>
Expand All @@ -529,6 +539,12 @@ exports[`AdminSearchPageComponent should snap with an expanded account requests
/>
</a>
</div>
<button
class="btn btn-success"
id="approve-account-request-0"
>
Approve
</button>
</div>
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,4 @@
</div>
</div>

<tm-account-request-table *ngIf="accountRequests.length" [accountRequests]="accountRequests" [searchString]="searchQuery"></tm-account-request-table>
<tm-account-request-table *ngIf="accountRequests.length" [accountRequests]="accountRequests" [searchString]="searchString"></tm-account-request-table>
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ describe('AdminSearchPageComponent', () => {
it('should show account request links when expand all button clicked', () => {
const accountRequestResult: AccountRequestTableRowModel = DEFAULT_ACCOUNT_REQUEST_SEARCH_RESULT;
component.accountRequests = [accountRequestResult];
component.searchQuery = 'test'; // To show the account request table
component.searchString = 'test'; // To show the account request table
fixture.detectChanges();

const button: any = fixture.debugElement.nativeElement.querySelector('#show-account-request-links');
Expand Down Expand Up @@ -961,7 +961,7 @@ describe('AdminSearchPageComponent', () => {
it('should show error message when resetting account request is unsuccessful', () => {
component.accountRequests = [DEFAULT_ACCOUNT_REQUEST_SEARCH_RESULT];
component.accountRequests[0].registeredAtText = 'Wed, 09 Feb 2022, 10:23 AM +00:00';
component.searchQuery = 'test';
component.searchString = 'test';
fixture.detectChanges();

jest.spyOn(ngbModal, 'open').mockImplementation(() => {
Expand All @@ -988,7 +988,7 @@ describe('AdminSearchPageComponent', () => {
it('should show success message when resetting account request is successful', () => {
component.accountRequests = [DEFAULT_ACCOUNT_REQUEST_SEARCH_RESULT];
component.accountRequests[0].registeredAtText = 'Wed, 09 Feb 2022, 10:23 AM +00:00';
component.searchQuery = 'test';
component.searchString = 'test';
fixture.detectChanges();

jest.spyOn(ngbModal, 'open').mockImplementation(() => {
Expand Down
40 changes: 39 additions & 1 deletion src/web/services/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
MessageOutput,
AccountRequestStatus,
} from '../types/api-output';
import { AccountCreateRequest } from '../types/api-request';
import { AccountCreateRequest, AccountRequestUpdateRequest } from '../types/api-request';

/**
* Handles account related logic provision
Expand Down Expand Up @@ -95,6 +95,44 @@ export class AccountService {
return this.httpRequestService.put(ResourceEndpoints.ACCOUNT_RESET, paramMap);
}

/**
* Approves account request by calling API
*/
approveAccountRequest(id: string, name: string, email: string, institute: string)
: Observable<MessageOutput> {
const paramMap: Record<string, string> = {
id,
};
const accountReqUpdateRequest : AccountRequestUpdateRequest = {
name,
email,
institute,
status: AccountRequestStatus.APPROVED,
};

return this.httpRequestService.put(ResourceEndpoints.ACCOUNT_REQUEST, paramMap, accountReqUpdateRequest);
}

/**
* Edits an account request by calling API.
*/
editAccountRequest(id: string, name: string, email: string, institute: string,
status: AccountRequestStatus, comments: string)
: Observable<AccountRequest> {
const paramMap: Record<string, string> = {
id,
};
const accountReqUpdateRequest : AccountRequestUpdateRequest = {
name,
email,
institute,
status,
comments,
};

return this.httpRequestService.put(ResourceEndpoints.ACCOUNT_REQUEST, paramMap, accountReqUpdateRequest);
}

/**
* Gets an account by calling API.
*/
Expand Down
5 changes: 3 additions & 2 deletions src/web/services/search.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ResourceEndpoints } from '../types/api-const';
import {
AccountRequest,
AccountRequests,
AccountRequestStatus,
Course, FeedbackSession,
FeedbackSessions,
Instructor,
Expand Down Expand Up @@ -307,7 +308,7 @@ export class SearchService {
registeredAtText: '',
registrationLink: '',
showLinks: false,
status: '',
status: AccountRequestStatus.PENDING,
comments: '',
};

Expand Down Expand Up @@ -474,7 +475,7 @@ export interface AccountRequestSearchResult {
id: string;
name: string;
email: string;
status: string;
status: AccountRequestStatus;
institute: string;
createdAtText: string;
registeredAtText: string | null;
Expand Down

0 comments on commit 62750b0

Please sign in to comment.