Skip to content

Commit

Permalink
[#11878] Create Rejection Modal for Account Requests (#12989)
Browse files Browse the repository at this point in the history
* Create rejection modal

* fix lint and tests

* fix placeholders and lint

* remove title

* integrate api

* check undefined title and body

* fix trailing white spaces

* fix whitespace

* change error message

* re-add account request table on home page

* replace support email
  • Loading branch information
domoberzin authored Apr 10, 2024
1 parent 50c87bc commit a36ecf7
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</tr>
</thead>
<tbody>
<ng-container *ngFor="let accountRequest of accountRequests; let i = index">
<ng-container *ngFor="let accountRequest of accountRequests; let i = index; trackBy: trackAccountRequest">
<tr>
<td [innerHtml]="accountRequest.name | highlighter:searchString:true">
<br>
Expand Down Expand Up @@ -66,10 +66,17 @@
<i class="fa-solid fa-eye"></i>
</a>
</div>
<button id="approve-account-request-{{i}}" class="btn btn-success" [disabled]="accountRequest.registeredAtText || accountRequest.status === 'APPROVED'" (click)="$event.stopPropagation(); approveAccountRequest(accountRequest)">Approve</button>
<span ngbDropdown container="body">
<button id="reject-account-request-{{i}}" type="button" class="btn btn-warning" [disabled]="accountRequest.registeredAtText || accountRequest.status === 'APPROVED' || accountRequest.status === 'REJECTED'" ngbDropdownToggle> Reject </button>
<div ngbDropdownMenu (click)="$event.stopPropagation()">
<button class="btn btn-light btn-sm dropdown-item" (click)="$event.stopPropagation(); rejectAccountRequest(accountRequest)"> Reject </button>
<button class="btn btn-light btn-sm dropdown-item" (click)="$event.stopPropagation(); rejectAccountRequestWithReason(accountRequest)"> Reject With Reason </button>
</div>
</span>
<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>
<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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Component, Input } from '@angular/core';
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 {
RejectWithReasonModalComponent,
} from './admin-reject-with-reason-modal/admin-reject-with-reason-modal.component';
import { AccountService } from '../../../services/account.service';
import { SimpleModalService } from '../../../services/simple-modal.service';
import { StatusMessageService } from '../../../services/status-message.service';
Expand Down Expand Up @@ -146,4 +149,39 @@ export class AccountRequestTableComponent {

modalRef.result.then(() => {}, () => {});
}

rejectAccountRequest(accountRequest: AccountRequestTableRowModel): void {
this.accountService.rejectAccountRequest(accountRequest.id)
.subscribe({
next: (resp : AccountRequest) => {
accountRequest.status = resp.status;
},
error: (resp: ErrorMessageOutput) => {
this.statusMessageService.showErrorToast(resp.error.message);
},
});
}

rejectAccountRequestWithReason(accountRequest: AccountRequestTableRowModel): void {
const modalRef: NgbModalRef = this.ngbModal.open(RejectWithReasonModalComponent);
modalRef.componentInstance.accountRequestName = accountRequest.name;
modalRef.componentInstance.accountRequestEmail = accountRequest.email;

modalRef.result.then(() => {
this.accountService.rejectAccountRequest(accountRequest.id,
modalRef.componentInstance.rejectionReasonTitle, modalRef.componentInstance.rejectionReasonBody)
.subscribe({
next: (resp: AccountRequest) => {
accountRequest.status = resp.status;
},
error: (resp: ErrorMessageOutput) => {
this.statusMessageService.showErrorToast(resp.error.message);
},
});
}, () => {});
}

trackAccountRequest(accountRequest: AccountRequestTableRowModel): string {
return accountRequest.id;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ 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 {
RejectWithReasonModalComponent,
} from './admin-reject-with-reason-modal/admin-reject-with-reason-modal.component';
import { Pipes } from '../../pipes/pipes.module';
import { RichTextEditorModule } from '../rich-text-editor/rich-text-editor.module';

Expand All @@ -14,6 +17,7 @@ import { RichTextEditorModule } from '../rich-text-editor/rich-text-editor.modul
declarations: [
AccountRequestTableComponent,
EditRequestModalComponent,
RejectWithReasonModalComponent,
],
exports: [
AccountRequestTableComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface RejectWithReasonModalComponentResult {
rejectionReasonTitle: string;
rejectionReasonBody: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div class="modal-header bg-warning">
<h5 class="modal-title">
<div>Reject Account Request for {{ accountRequestName }} With Reason</div>
</h5>
<button type="button" class="btn-close" (click)="activeModal.dismiss()"></button>
</div>
<div id="reject-account-request-modal" class="modal-body">
<div class="row">
<div class="col-12">
<div>
<label><b>Rejection Message Title:</b></label>
</div>
<div class="form-group">
<input id="rejection-reason-title" type="text" class="form-control" rows="5" value="{{ rejectionReasonTitle }}" (input)="rejectionReasonTitle = $event.target.value">
</div>
<div>
<label><b>Rejection Message Body:</b></label>
</div>
<tm-rich-text-editor id="rejection-reason-body" [richText]="rejectionReasonBody" (richTextChange)="onRejectionReasonBodyChange($event)"></tm-rich-text-editor>
</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-warning" (click)="reject()">
Reject
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { Component, Input, OnInit } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { RejectWithReasonModalComponentResult } from './admin-reject-with-reason-modal-model';
import { environment } from '../../../../environments/environment';
import { StatusMessageService } from '../../../../services/status-message.service';

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

export class RejectWithReasonModalComponent implements OnInit {

@Input()
accountRequestName: string = '';

@Input()
accountRequestEmail: string = '';

rejectionReasonBody: string = '<p>Hi, {accountRequestName} </p>\n\n'
+ '<p>Thanks for your interest in using TEAMMATES. '
+ 'We are unable to create a TEAMMATES instructor account for you.</p>'
+ '<p><strong>Reason:</strong> The email address you provided is not an &#39;official&#39; '
+ 'email address provided by your institution.<br />'
+ '<strong>Remedy:</strong> Please re-submit an account request with your &#39;official&#39; '
+ 'institution email address.</p>\n\n'
+ '<p><strong>Reason:</strong> The email address you have provided does seems like it belongs to a student '
+ '(i.e., not a staff member) of your institution.<br />'
+ '<strong>Remedy:</strong> If you are a student but you still need an instructor account, '
+ 'please send your justification to {supportEmail}</p>\n\n'
+ '<p><strong>Reason:</strong> You already have an account for this email address and this institution.<br />'
+ '<strong>Remedy:</strong> You can login to TEAMMATES using your Google account {existingEmail} </p>\n\n'
+ '<p>If you need further clarification or would like to appeal this decision, please '
+ 'feel free to contact us at {supportEmail}</p>'
+ '<p>Regards,<br />TEAMMATES Team.</p>';
rejectionReasonTitle: string = 'We are Unable to Create an Account for you';

constructor(public activeModal: NgbActiveModal, public statusMessageService: StatusMessageService) {}

ngOnInit(): void {
this.rejectionReasonBody = this.rejectionReasonBody.replace('{accountRequestName}', this.accountRequestName);
this.rejectionReasonBody = this.rejectionReasonBody.replace('{existingEmail}', this.accountRequestEmail);
this.rejectionReasonBody = this.rejectionReasonBody.replaceAll('{supportEmail}', environment.supportEmail);
}

onRejectionReasonBodyChange(updatedText: string): void {
this.rejectionReasonBody = updatedText;
}

/**
* Fires the reject event.
*/
reject(): void {

if (!this.rejectionReasonBody) {
this.statusMessageService.showErrorToast('Please provide an email body for the rejection email.');
return;
}

if (!this.rejectionReasonTitle) {
this.statusMessageService.showErrorToast('Please provide a title for the rejection email.');
return;
}

const result: RejectWithReasonModalComponentResult = {
rejectionReasonTitle: this.rejectionReasonTitle,
rejectionReasonBody: this.rejectionReasonBody,
};

this.activeModal.close(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,5 @@
</div>
</div>
</div>

<tm-account-request-table *ngIf="accountReqs.length" [accountRequests]="accountReqs" [searchString]=""></tm-account-request-table>
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,36 @@ exports[`AdminSearchPageComponent should snap with an expanded account requests
>
Approve
</button>
<span
class="dropdown"
container="body"
ngbdropdown=""
>
<button
aria-expanded="false"
class="dropdown-toggle btn btn-warning"
id="reject-account-request-0"
ngbdropdowntoggle=""
type="button"
>
Reject
</button>
<div
class="dropdown-menu"
ngbdropdownmenu=""
>
<button
class="btn btn-light btn-sm dropdown-item"
>
Reject
</button>
<button
class="btn btn-light btn-sm dropdown-item"
>
Reject With Reason
</button>
</div>
</span>
</div>
</td>
</tr>
Expand Down
26 changes: 25 additions & 1 deletion src/web/services/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
MessageOutput,
AccountRequestStatus,
} from '../types/api-output';
import { AccountCreateRequest, AccountRequestUpdateRequest } from '../types/api-request';
import {
AccountCreateRequest,
AccountRequestUpdateRequest,
AccountRequestRejectionRequest,
} from '../types/api-request';

/**
* Handles account related logic provision
Expand Down Expand Up @@ -164,4 +168,24 @@ export class AccountService {
return this.httpRequestService.get(ResourceEndpoints.ACCOUNT_REQUESTS, paramMap);
}

/**
* Rejects an account request by calling API.
*/
rejectAccountRequest(id: string, title?: string, body?: string): Observable<AccountRequest> {
let accountReqRejectRequest: AccountRequestRejectionRequest = {};

if (title !== undefined && body !== undefined) {
accountReqRejectRequest = {
reasonTitle: title,
reasonBody: body,
};
}

const paramMap: Record<string, string> = {
id,
};

return this.httpRequestService.post(ResourceEndpoints.ACCOUNT_REQUEST_REJECT, paramMap, accountReqRejectRequest);
}

}

0 comments on commit a36ecf7

Please sign in to comment.