Skip to content

Commit

Permalink
Feat: Add endorsement reasons (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
RikudouSage authored Sep 14, 2023
1 parent cf54ec5 commit 7bc7fe3
Show file tree
Hide file tree
Showing 13 changed files with 279 additions and 22 deletions.
19 changes: 13 additions & 6 deletions src/app/endorsements/endorsements.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {RouterModule, Routes} from "@angular/router";
import { MyEndorsementsComponent } from './pages/my-endorsements/my-endorsements.component';
import { EndorseInstanceComponent } from './pages/endorse-instance/endorse-instance.component';
import {MyEndorsementsComponent} from './pages/my-endorsements/my-endorsements.component';
import {EndorseInstanceComponent} from './pages/endorse-instance/endorse-instance.component';
import {ReactiveFormsModule} from "@angular/forms";
import {Guards} from "../guards/guards";
import {SharedModule} from "../shared/shared.module";
import {EditEndorsementReasonsComponent} from './pages/edit-endorsement-reasons/edit-endorsement-reasons.component';

const routes: Routes = [
{
Expand All @@ -17,13 +18,19 @@ const routes: Routes = [
path: 'endorse',
component: EndorseInstanceComponent,
canActivate: [Guards.isLoggedIn()],
}
},
{
path: 'my/edit/:instance',
component: EditEndorsementReasonsComponent,
canActivate: [Guards.isLoggedIn()],
},
];

@NgModule({
declarations: [
MyEndorsementsComponent,
EndorseInstanceComponent
EndorseInstanceComponent,
EditEndorsementReasonsComponent
],
imports: [
CommonModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<app-loader *ngIf="loading else content"></app-loader>

<ng-template #content>
<div class="col-md-12">
<div class="card">
<div class="card-body">
<p>Endorsing means that you approve of an instance. For more information visit the <a routerLink="/glossary">Glossary</a>.</p>
<p><strong>You can do this only if your instance is guaranteed by some other instance.</strong></p>
<form [formGroup]="form" (submit)="updateReasons()">
<div class="form-group">
<label for="inputInstance">Instance to endorse</label>
<input class="form-control" type="text" id="inputInstance" formControlName="instance" />
</div>
<div class="form-group">
<label for="inputReason">Reasons</label>
<select formControlName="reasons" id="inputReason" multiple tom-select [maxItems]="null" [create]="true">
<option *ngFor="let option of availableReasons" [value]="option">{{option}}</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Update</button>
</form>
</div>
</div>
</div>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
min-width: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import {Component, OnInit} from '@angular/core';
import {FormControl, FormGroup, Validators} from "@angular/forms";
import {TitleService} from "../../../services/title.service";
import {MessageService} from "../../../services/message.service";
import {FediseerApiService} from "../../../services/fediseer-api.service";
import {ActivatedRoute, Router} from "@angular/router";
import {AuthenticationManagerService} from "../../../services/authentication-manager.service";
import {ApiResponseHelperService} from "../../../services/api-response-helper.service";
import {toPromise} from "../../../types/resolvable";
import {map} from "rxjs";
import {NormalizedInstanceDetailResponse} from "../../../response/normalized-instance-detail.response";

@Component({
selector: 'app-edit-endorsement-reasons',
templateUrl: './edit-endorsement-reasons.component.html',
styleUrls: ['./edit-endorsement-reasons.component.scss']
})
export class EditEndorsementReasonsComponent implements OnInit {
public form = new FormGroup({
instance: new FormControl<string>({value: '', disabled: true}, [Validators.required]),
reasons: new FormControl<string[]>([]),
});
public loading: boolean = true;
public availableReasons: string[] = [];

constructor(
private readonly titleService: TitleService,
private readonly messageService: MessageService,
private readonly api: FediseerApiService,
private readonly router: Router,
private readonly activatedRoute: ActivatedRoute,
private readonly authManager: AuthenticationManagerService,
private readonly apiResponseHelper: ApiResponseHelperService,
) {
}

public async ngOnInit(): Promise<void> {
this.titleService.title = 'Update endorsement reasons';

this.activatedRoute.params.subscribe(async params => {
const targetInstance = params['instance'] as string;
let availableReasons = await toPromise(this.api.usedEndorsementReasons);
if (availableReasons === null) {
this.messageService.createWarning(`Couldn't get list of reasons that were used previously, autocompletion won't work.`);
availableReasons = [];
}
this.availableReasons = availableReasons;

const existing = await toPromise(
this.api.getEndorsementsByInstance([this.authManager.currentInstanceSnapshot.name]).pipe(
map(response => {
if (this.apiResponseHelper.handleErrors([response])) {
return null;
}

const instance = response.successResponse!.instances.filter(
instance => instance.domain === targetInstance,
);
if (!instance.length) {
this.messageService.createError(`Couldn't find this instance amongst your endorsements. Are you sure you've endorsed it?`);
return null;
}

return instance[0];
}),
),
);

if (existing === null) {
this.loading = false;
return;
}

this.form.patchValue({
instance: existing.domain,
reasons: NormalizedInstanceDetailResponse.fromInstanceDetail(existing).unmergedEndorsementReasons,
});
this.loading = false;
});
}

public async updateReasons(): Promise<void> {
if (!this.form.valid) {
this.messageService.createError("The form is not valid, please make sure all fields are filled correctly.");
return;
}

this.loading = true;
this.api.updateEndorsement(
this.form.controls.instance.value!,
this.form.controls.reasons.value ? this.form.controls.reasons.value!.join(',') : null,
).subscribe(response => {
if (this.apiResponseHelper.handleErrors([response])) {
this.loading = false;
return;
}

this.loading = false;
this.router.navigateByUrl('/endorsements/my').then(() => {
this.messageService.createSuccess(`${this.form.controls.instance.value} was successfully updated!`);
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
<label for="inputInstance">Instance to endorse</label>
<input class="form-control" type="text" id="inputInstance" formControlName="instance" />
</div>
<div class="form-group">
<label for="inputReason">Reasons</label>
<select formControlName="reasons" id="inputReason" multiple tom-select [maxItems]="null" [create]="true">
<option *ngFor="let option of availableReasons" [value]="option">{{option}}</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Endorse</button>
</form>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {FormControl, FormGroup, Validators} from "@angular/forms";
import {MessageService} from "../../../services/message.service";
import {FediseerApiService} from "../../../services/fediseer-api.service";
import {Router} from "@angular/router";
import {ApiResponseHelperService} from "../../../services/api-response-helper.service";
import {toPromise} from "../../../types/resolvable";

@Component({
selector: 'app-endorse-instance',
Expand All @@ -13,8 +15,10 @@ import {Router} from "@angular/router";
export class EndorseInstanceComponent implements OnInit {
public form = new FormGroup({
instance: new FormControl<string>('', [Validators.required]),
reasons: new FormControl<string[]>([]),
});
public loading: boolean = false;
public loading: boolean = true;
public availableReasons: string[] = [];

constructor(
private readonly titleService: TitleService,
Expand All @@ -25,6 +29,15 @@ export class EndorseInstanceComponent implements OnInit {
}
public async ngOnInit(): Promise<void> {
this.titleService.title = 'Endorse an instance';

const reasons = await toPromise(this.api.usedEndorsementReasons);
if (reasons === null) {
this.messageService.createWarning('Getting list of reasons failed, there will not be any autocompletion.');
} else {
this.availableReasons = reasons;
}

this.loading = false;
}

public async doEndorse(): Promise<void> {
Expand All @@ -34,7 +47,10 @@ export class EndorseInstanceComponent implements OnInit {
}

this.loading = true;
this.api.endorseInstance(this.form.controls.instance.value!).subscribe(response => {
this.api.endorseInstance(
this.form.controls.instance.value!,
this.form.controls.reasons.value ? this.form.controls.reasons.value!.join(',') : null,
).subscribe(response => {
if (!response.success) {
this.messageService.createError(`There was an api error: ${response.errorResponse!.message}`);
this.loading = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@ <h3 class="card-title">Endorsements that {{instance.name}} <strong>received</str
<thead>
<tr>
<th>{{ "Instance" }}</th>
<th>Reasons</th>
</tr>
</thead>
<tbody>
<tr *ngIf="!endorsementsForMyInstance.length">
<td>This instance didn't receive any endorsements yet.</td>
<td colspan="2">This instance didn't receive any endorsements yet.</td>
</tr>
<tr *ngFor="let endorsed of endorsementsForMyInstance">
<td><a routerLink="/instances/detail/{{endorsed.domain}}">{{endorsed.domain}}</a></td>
<td>
<ul *ngIf="endorsed.endorsementReasons.length">
<li *ngFor="let reason of endorsed.endorsementReasons">{{reason}}</li>
</ul>
<code *ngIf="!endorsed.endorsementReasons.length">N/A</code>
</td>
</tr>
</tbody>
</table>
Expand All @@ -35,8 +42,8 @@ <h3 class="card-title">Endorsements that {{instance.name}} <strong>has given</st
<thead>
<tr>
<th>Instance</th>
<th>Endorsements</th>
<th>Cancel</th>
<th>Reasons</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
Expand All @@ -51,8 +58,15 @@ <h3 class="card-title">Endorsements that {{instance.name}} <strong>has given</st
</tr>
<tr *ngFor="let endorsed of endorsementsByMyInstance">
<td><a routerLink="/instances/detail/{{endorsed.domain}}">{{endorsed.domain}}</a></td>
<td>{{endorsed.endorsements}}</td>
<td>
<ul *ngIf="endorsed.endorsementReasons.length">
<li *ngFor="let reason of endorsed.endorsementReasons">{{reason}}</li>
</ul>
<code *ngIf="!endorsed.endorsementReasons.length">N/A</code>
</td>
<td>
<a routerLink="/endorsements/my/edit/{{endorsed.domain}}" type="button" class="btn btn-primary">Edit</a>
&nbsp;
<button class="btn btn-danger" (click)="cancelEndorsement(endorsed.domain)">Cancel</button>
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import {Observable} from "rxjs";
import {Instance} from "../../../user/instance";
import {toObservable, toPromise} from "../../../types/resolvable";
import {ApiResponseHelperService} from "../../../services/api-response-helper.service";
import {NormalizedInstanceDetailResponse} from "../../../response/normalized-instance-detail.response";

@Component({
selector: 'app-my-endorsements',
templateUrl: './my-endorsements.component.html',
styleUrls: ['./my-endorsements.component.scss']
})
export class MyEndorsementsComponent implements OnInit {
public endorsementsForMyInstance: InstanceDetailResponse[] = [];
public endorsementsByMyInstance: InstanceDetailResponse[] = [];
public endorsementsForMyInstance: NormalizedInstanceDetailResponse[] = [];
public endorsementsByMyInstance: NormalizedInstanceDetailResponse[] = [];
public instance: Observable<Instance> = this.authManager.currentInstance;
public guaranteed: boolean = false;
public loading: boolean = true;
Expand Down Expand Up @@ -44,8 +45,12 @@ export class MyEndorsementsComponent implements OnInit {
return;
}

this.endorsementsForMyInstance = responses[0].successResponse!.instances;
this.endorsementsByMyInstance = responses[1].successResponse!.instances;
this.endorsementsForMyInstance = responses[0].successResponse!.instances.map(
instance => NormalizedInstanceDetailResponse.fromInstanceDetail(instance),
);
this.endorsementsByMyInstance = responses[1].successResponse!.instances.map(
instance => NormalizedInstanceDetailResponse.fromInstanceDetail(instance),
);
this.guaranteed = responses[2].successResponse!.guarantor !== undefined;
this.loading = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,18 @@ <h3 class="card-title" id="endorsements-received">Endorsements received ({{endor
<thead>
<tr>
<th>Instance</th>
<th>Reasons</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let instance of endorsementsReceived">
<td><a routerLink="/instances/detail/{{instance.domain}}">{{instance.domain}}</a></td>
<td>
<ul *ngIf="instance.endorsementReasons.length">
<li *ngFor="let reason of instance.endorsementReasons">{{reason}}</li>
</ul>
<code *ngIf="!instance.endorsementReasons.length">N/A</code>
</td>
</tr>
</tbody>
</table>
Expand All @@ -199,11 +206,18 @@ <h3 class="card-title" id="endorsements-given">Endorsements given ({{endorsement
<thead>
<tr>
<th>Instance</th>
<th>Reasons</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let instance of endorsementsGiven">
<td><a routerLink="/instances/detail/{{instance.domain}}">{{instance.domain}}</a></td>
<td>
<ul *ngIf="instance.endorsementReasons.length">
<li *ngFor="let reason of instance.endorsementReasons">{{reason}}</li>
</ul>
<code *ngIf="!instance.endorsementReasons.length">N/A</code>
</td>
</tr>
</tbody>
</table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export class InstanceDetailComponent implements OnInit {
public censuresGiven: NormalizedInstanceDetailResponse[] | null = null;
public hesitationsReceived: NormalizedInstanceDetailResponse[] | null = null;
public hesitationsGiven: NormalizedInstanceDetailResponse[] | null = null;
public endorsementsReceived: InstanceDetailResponse[] | null = null;
public endorsementsGiven: InstanceDetailResponse[] | null = null;
public endorsementsReceived: NormalizedInstanceDetailResponse[] | null = null;
public endorsementsGiven: NormalizedInstanceDetailResponse[] | null = null;
public guaranteesGiven: InstanceDetailResponse[] | null = null;
public detail: InstanceDetailResponse | null = null;

Expand Down Expand Up @@ -70,8 +70,12 @@ export class InstanceDetailComponent implements OnInit {
this.censuresGiven = responses[1].successResponse?.instances.map(
instance => NormalizedInstanceDetailResponse.fromInstanceDetail(instance),
) ?? null;
this.endorsementsReceived = responses[2].successResponse?.instances ?? null;
this.endorsementsGiven = responses[3].successResponse?.instances ?? null;
this.endorsementsReceived = responses[2].successResponse?.instances.map(
instance => NormalizedInstanceDetailResponse.fromInstanceDetail(instance),
) ?? null;
this.endorsementsGiven = responses[3].successResponse?.instances.map(
instance => NormalizedInstanceDetailResponse.fromInstanceDetail(instance),
) ?? null;
this.guaranteesGiven = responses[4].successResponse?.instances ?? null;
this.detail = responses[5].successResponse ?? null;
this.myInstance = !this.authManager.currentInstanceSnapshot.anonymous && this.detail?.domain === this.authManager.currentInstanceSnapshot.name;
Expand Down
1 change: 1 addition & 0 deletions src/app/response/instance-detail.response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export interface InstanceDetailResponse {
censure_evidence?: string[];
hesitation_reasons?: string[];
hesitation_evidence?: string[];
endorsement_reasons?: string[] | null;
}
Loading

0 comments on commit 7bc7fe3

Please sign in to comment.