Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#11878] Create Rejection Modal for Account Requests #12989

Merged
merged 14 commits into from
Apr 10, 2024
Merged
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);
}

}
Loading