Fetch data from cic meta and patch to user details.
This commit is contained in:
parent
3286c1df82
commit
ef1999dc0d
@ -113,8 +113,7 @@
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json",
|
||||
"tsconfig.worker.json"
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
|
@ -4,11 +4,11 @@ import {Observable, of, throwError} from 'rxjs';
|
||||
import {delay, dematerialize, materialize, mergeMap} from 'rxjs/operators';
|
||||
|
||||
const accounts = [
|
||||
{id: 1, name: 'John Doe', phone: '+25412345678', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '08/16/2020', balance: '12987', failedPinAttempts: 1, status: 'approved', bio: 'Bodaboda', category: 'transport', gender: 'male', location: 'Bofu', token: 'RSV', referrer: 'Jane Buck'},
|
||||
{id: 2, name: 'Jane Buck', phone: '+25412341234', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'vendor', created: '04/02/2020', balance: '56281', failedPinAttempts: 0, status: 'approved', bio: 'Groceries', category: 'food/water', gender: 'female', location: 'Lindi', token: 'ERN', referrer: ''},
|
||||
{id: 3, name: 'Mc Donald', phone: '+25498765432', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'group', created: '11/16/2020', balance: '450', failedPinAttempts: 2, status: 'unapproved', bio: 'Food', category: 'food/water', gender: 'male', location: 'Miyani', token: 'RSV', referrer: 'Hera Cles'},
|
||||
{id: 4, name: 'Hera Cles', phone: '+25498769876', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '05/28/2020', balance: '5621', failedPinAttempts: 3, status: 'approved', bio: 'Shop', category: 'shop', gender: 'female', location: 'Kayaba', token: 'BRT', referrer: 'Jane Buck'},
|
||||
{id: 5, name: 'Silver Fia', phone: '+25462518374', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'tokenAgent', created: '10/10/2020', balance: '817', failedPinAttempts: 0, status: 'unapproved', bio: 'Electronics', category: 'shop', gender: 'male', location: 'Mkanyeni', token: 'RSV', referrer: 'John Doe'},
|
||||
{id: 1, name: 'John Doe', phone: '+25412345678', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', age: 43, created: '08/16/2020', balance: '12987', failedPinAttempts: 1, status: 'active', bio: 'Bodaboda', category: 'transport', gender: 'male', location: 'Bofu', locationType: 'Rural', token: 'RSV', referrer: 'Jane Buck'},
|
||||
{id: 2, name: 'Jane Buck', phone: '+25412341234', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'vendor', age: 25, created: '04/02/2020', balance: '56281', failedPinAttempts: 0, status: 'active', bio: 'Groceries', category: 'food/water', gender: 'female', location: 'Lindi', locationType: 'Urban', token: 'ERN', referrer: ''},
|
||||
{id: 3, name: 'Mc Donald', phone: '+25498765432', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'group', age: 31, created: '11/16/2020', balance: '450', failedPinAttempts: 2, status: 'blocked', bio: 'Food', category: 'food/water', gender: 'male', location: 'Miyani', locationType: 'Rural', token: 'RSV', referrer: 'Hera Cles'},
|
||||
{id: 4, name: 'Hera Cles', phone: '+25498769876', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', age: 38, created: '05/28/2020', balance: '5621', failedPinAttempts: 3, status: 'active', bio: 'Shop', category: 'shop', gender: 'female', location: 'Kayaba', locationType: 'Urban', token: 'BRT', referrer: 'Jane Buck'},
|
||||
{id: 5, name: 'Silver Fia', phone: '+25462518374', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'tokenAgent', age: 19, created: '10/10/2020', balance: '817', failedPinAttempts: 0, status: 'blocked', bio: 'Electronics', category: 'shop', gender: 'male', location: 'Mkanyeni', locationType: 'Rural', token: 'RSV', referrer: 'John Doe'},
|
||||
];
|
||||
|
||||
const actions = [
|
||||
|
@ -11,8 +11,8 @@ import {User} from 'cic-client-meta';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
headers: HttpHeaders = new HttpHeaders({'x-cic-automerge': 'client'});
|
||||
keystore: MutableKeyStore = new MutablePgpKeyStore();
|
||||
syncableAccount: Syncable;
|
||||
signer: Signer = new PGPSigner(this.keystore);
|
||||
|
||||
accounts: any = '';
|
||||
@ -34,33 +34,14 @@ export class UserService {
|
||||
return this.http.get(`${environment.cicUssdUrl}/pin`, { params });
|
||||
}
|
||||
|
||||
createAccount(accountType: string, idNumber: string, phoneNumber: string, givenName: string, surname: string, directoryEntry: string,
|
||||
location: string, gender: string, referrer: string, businessCategory: string): Observable<any> {
|
||||
console.log(accountType);
|
||||
return this.http.post(
|
||||
`${environment.cicUssdUrl}/user`,
|
||||
{
|
||||
accountType: accountType,
|
||||
idNumber: idNumber,
|
||||
phone: phoneNumber,
|
||||
givenName: givenName,
|
||||
surname: surname,
|
||||
directoryEntry: directoryEntry,
|
||||
location: location,
|
||||
gender: gender,
|
||||
referrer: referrer,
|
||||
businessCategory: businessCategory
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async changeAccountInfo(address: string, status: string, name: string, phoneNumber: string, type: string, token: string,
|
||||
async changeAccountInfo(address: string, status: string, name: string, phoneNumber: string, age: string, type: string, token: string,
|
||||
failedPinAttempts: string, bio: string, gender: string, businessCategory: string, userLocation: string,
|
||||
location: string, referrer: string): Promise<any> {
|
||||
location: string, locationType: string, referrer: string): Promise<any> {
|
||||
const accountDetails = {
|
||||
status,
|
||||
name,
|
||||
phoneNumber,
|
||||
age,
|
||||
type,
|
||||
token,
|
||||
failedPinAttempts,
|
||||
@ -69,31 +50,31 @@ export class UserService {
|
||||
businessCategory,
|
||||
userLocation,
|
||||
location,
|
||||
locationType,
|
||||
referrer
|
||||
};
|
||||
const accountKey = await User.toKey(address);
|
||||
const headers = new HttpHeaders({'x-cic-automerge': 'client'});
|
||||
this.http.get(`${environment.cicMetaUrl}/${accountKey}`, { headers }).pipe(first()).subscribe(async res => {
|
||||
this.syncableAccount = Envelope.fromJSON(res).unwrap();
|
||||
this.http.get<JSON>(`${environment.cicMetaUrl}/${accountKey}`, { headers: this.headers }).pipe(first()).subscribe(async res => {
|
||||
const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
||||
let update = [];
|
||||
for (const prop in accountDetails) {
|
||||
update.push(new ArgPair(prop, accountDetails[prop]));
|
||||
}
|
||||
this.syncableAccount.update(update, 'client-branch');
|
||||
await this.updateMeta(accountKey, headers);
|
||||
syncableAccount.update(update, 'client-branch');
|
||||
await this.updateMeta(syncableAccount, accountKey, this.headers);
|
||||
}, async error => {
|
||||
console.error('There is an error!', error);
|
||||
const refName = accountKey + ':cic.person';
|
||||
this.syncableAccount = new Syncable(refName, accountDetails);
|
||||
await this.updateMeta(accountKey, headers);
|
||||
const syncableAccount: Syncable = new Syncable(refName, accountDetails);
|
||||
await this.updateMeta(syncableAccount, accountKey, this.headers);
|
||||
});
|
||||
return accountKey;
|
||||
}
|
||||
|
||||
async updateMeta(accountKey: string, headers: HttpHeaders): Promise<any> {
|
||||
const envelope = await this.wrap(this.syncableAccount , this.signer);
|
||||
async updateMeta(syncableAccount: Syncable, accountKey: string, headers: HttpHeaders): Promise<any> {
|
||||
const envelope = await this.wrap(syncableAccount , this.signer);
|
||||
const reqBody = envelope.toJSON();
|
||||
this.http.put(`${environment.cicMetaUrl}/${accountKey}`, { data: reqBody }, { headers }).pipe(first()).subscribe(res => {
|
||||
this.http.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody , { headers }).pipe(first()).subscribe(res => {
|
||||
console.log(res);
|
||||
});
|
||||
}
|
||||
@ -146,11 +127,13 @@ export class UserService {
|
||||
return this.http.post(`${environment.cicCacheUrl}/staff/${id}`, {accountType: type});
|
||||
}
|
||||
|
||||
getAccountDetailsFromMeta(userKey: string): Observable<any> {
|
||||
return this.http.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers });
|
||||
}
|
||||
|
||||
getUser(userKey: string): any {
|
||||
const headers = new HttpHeaders({'x-cic-automerge': 'client'});
|
||||
this.http.get(`${environment.cicMetaUrl}/${userKey}`, { headers }).pipe(first()).subscribe(async res => {
|
||||
const fetchedAccount = Envelope.fromJSON(res).unwrap();
|
||||
return fetchedAccount.m.data;
|
||||
this.http.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers }).pipe(first()).subscribe(async res => {
|
||||
return Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
</h3>
|
||||
<span class="ml-auto"><strong>Balance:</strong> {{account?.balance}} RCU</span>
|
||||
<span class="ml-2"><strong>Created:</strong> {{account?.created}}</span>
|
||||
<span class="ml-2"><strong>Address:</strong> {{account?.address}}</span>
|
||||
<span class="ml-2"><strong>Address:</strong><a href="{{bloxbergLink}}" target="_blank"> {{account?.address}} </a></span>
|
||||
</div>
|
||||
</div>
|
||||
<app-disbursement *ngIf="isDisbursing" (cancelDisbursmentEvent)="addTransfer()" [account]="account">
|
||||
@ -56,8 +56,8 @@
|
||||
<mat-label> STATUS </mat-label>
|
||||
<mat-select id="status" [(value)]="account.status" formControlName="status"
|
||||
[errorStateMatcher]="matcher">
|
||||
<mat-option value="unapproved">Unapproved</mat-option>
|
||||
<mat-option value="approved">Approved</mat-option>
|
||||
<mat-option value="blocked">Blocked</mat-option>
|
||||
<mat-option value="active">Active</mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="submitted && accountInfoFormStub.status.errors">Status is required.</mat-error>
|
||||
</mat-form-field>
|
||||
@ -81,6 +81,15 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Age: </mat-label>
|
||||
<input matInput type="text" id="age" placeholder="{{account?.age}}"
|
||||
value="{{account?.age}}" formControlName="age" [errorStateMatcher]="matcher">
|
||||
<mat-error *ngIf="submitted && accountInfoFormStub.age.errors">Age is required.</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label> ACCOUNT TYPE: </mat-label>
|
||||
@ -196,6 +205,20 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label> LOCATION TYPE: </mat-label>
|
||||
<mat-select id="locationType" [(value)]="account.locationType" formControlName="locationType"
|
||||
[errorStateMatcher]="matcher">
|
||||
<mat-option value="Urban"> URBAN </mat-option>
|
||||
<mat-option value="Periurban"> PERIURBAN </mat-option>
|
||||
<mat-option value="Rural"> RURAL </mat-option>
|
||||
<mat-option value="Other"> OTHER </mat-option>
|
||||
</mat-select>
|
||||
<mat-error *ngIf="submitted && accountInfoFormStub.locationType.errors">Location Type is required.</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Referred By: </mat-label>
|
||||
@ -256,10 +279,10 @@
|
||||
<td>{{account?.type}}</td>
|
||||
<td>{{account?.created}}</td>
|
||||
<td>
|
||||
<span *ngIf="account?.status === 'approved'" class="badge badge-success badge-pill">
|
||||
<span *ngIf="account?.status === 'active'" class="badge badge-success badge-pill">
|
||||
{{account?.status}}
|
||||
</span>
|
||||
<span *ngIf="account?.status === 'unapproved'" class="badge badge-danger badge-pill">
|
||||
<span *ngIf="account?.status === 'blocked'" class="badge badge-danger badge-pill">
|
||||
{{account?.status}}
|
||||
</span>
|
||||
</td>
|
||||
|
@ -8,6 +8,8 @@ import {ActivatedRoute, Params, Router} from '@angular/router';
|
||||
import {first} from 'rxjs/operators';
|
||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
||||
import {CustomErrorStateMatcher} from '@app/_helpers';
|
||||
import {User} from 'cic-client-meta';
|
||||
import {Envelope} from '@src/assets/js/cic-meta/sync';
|
||||
|
||||
@Component({
|
||||
selector: 'app-account-details',
|
||||
@ -32,6 +34,7 @@ export class AccountDetailsComponent implements OnInit {
|
||||
|
||||
accountInfoForm: FormGroup;
|
||||
account: any;
|
||||
metaAccount: any;
|
||||
accounts: any[] = [];
|
||||
accountsType = 'all';
|
||||
initialSelection = [];
|
||||
@ -46,6 +49,7 @@ export class AccountDetailsComponent implements OnInit {
|
||||
transactionsType = 'all';
|
||||
matcher = new CustomErrorStateMatcher();
|
||||
submitted: boolean = false;
|
||||
bloxbergLink: string;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
@ -59,6 +63,7 @@ export class AccountDetailsComponent implements OnInit {
|
||||
status: ['', Validators.required],
|
||||
name: ['', Validators.required],
|
||||
phoneNumber: ['', Validators.required],
|
||||
age: ['', Validators.required],
|
||||
type: ['', Validators.required],
|
||||
token: ['', Validators.required],
|
||||
failedPinAttempts: ['', Validators.required],
|
||||
@ -67,20 +72,38 @@ export class AccountDetailsComponent implements OnInit {
|
||||
businessCategory: ['', Validators.required],
|
||||
userLocation: ['', Validators.required],
|
||||
location: ['', Validators.required],
|
||||
locationType: ['', Validators.required],
|
||||
referrer: ['', Validators.required]
|
||||
});
|
||||
this.route.paramMap.subscribe((params: Params) => {
|
||||
this.userService.getAccountById(params.get('id')).pipe(first()).subscribe(account => {
|
||||
this.userService.getAccountById(params.get('id')).pipe(first()).subscribe(async account => {
|
||||
this.account = account;
|
||||
this.userService.getHistoryByUser(this.account?.id).pipe(first()).subscribe(history => {
|
||||
this.historyDataSource = new MatTableDataSource<any>(history);
|
||||
this.historyDataSource.paginator = this.historyTablePaginator;
|
||||
this.historyDataSource.sort = this.historyTableSort;
|
||||
this.userService.getAccountDetailsFromMeta(await User.toKey(account.address)).pipe(first()).subscribe(res => {
|
||||
this.metaAccount = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
||||
const accountInfo = this.metaAccount.m.data;
|
||||
console.log(accountInfo);
|
||||
this.accountInfoForm.patchValue({
|
||||
status: accountInfo.status,
|
||||
name: accountInfo.name,
|
||||
phoneNumber: accountInfo.phoneNumber,
|
||||
age: accountInfo.age,
|
||||
type: accountInfo.type,
|
||||
token: accountInfo.token,
|
||||
failedPinAttempts: accountInfo.failedPinAttempts,
|
||||
bio: accountInfo.bio,
|
||||
gender: accountInfo.gender,
|
||||
businessCategory: accountInfo.businessCategory,
|
||||
userLocation: accountInfo.userLocation,
|
||||
location: accountInfo.location,
|
||||
locationType: accountInfo.locationType,
|
||||
referrer: accountInfo.referrer
|
||||
});
|
||||
}, error => {
|
||||
this.accountInfoForm.patchValue({
|
||||
status: account.status,
|
||||
name: account.name,
|
||||
phoneNumber: account.phone,
|
||||
age: account.age,
|
||||
type: account.type,
|
||||
token: account.token,
|
||||
failedPinAttempts: account.failedPinAttempts,
|
||||
@ -89,9 +112,17 @@ export class AccountDetailsComponent implements OnInit {
|
||||
businessCategory: account.category,
|
||||
// userLocation: account.userLocation,
|
||||
location: account.location,
|
||||
locationType: account.locationType,
|
||||
referrer: account.referrer
|
||||
});
|
||||
});
|
||||
this.bloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.account.address + '/transactions';
|
||||
this.userService.getHistoryByUser(this.account?.id).pipe(first()).subscribe(history => {
|
||||
this.historyDataSource = new MatTableDataSource<any>(history);
|
||||
this.historyDataSource.paginator = this.historyTablePaginator;
|
||||
this.historyDataSource.sort = this.historyTableSort;
|
||||
});
|
||||
});
|
||||
});
|
||||
this.userService.getAccounts();
|
||||
this.locationService.getLocations();
|
||||
@ -141,15 +172,6 @@ export class AccountDetailsComponent implements OnInit {
|
||||
}
|
||||
|
||||
viewAccount(account): void {
|
||||
(function smoothscroll(): void {
|
||||
const currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
if (currentScroll > 0) {
|
||||
window.requestAnimationFrame(smoothscroll);
|
||||
window.scrollTo(0, currentScroll - (currentScroll / 8));
|
||||
}
|
||||
})();
|
||||
window.scrollTo(0, 0);
|
||||
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
||||
this.router.navigateByUrl(`/accounts/${account.id}`);
|
||||
}
|
||||
|
||||
@ -163,6 +185,7 @@ export class AccountDetailsComponent implements OnInit {
|
||||
this.accountInfoFormStub.status.value,
|
||||
this.accountInfoFormStub.name.value,
|
||||
this.accountInfoFormStub.phoneNumber.value,
|
||||
this.accountInfoFormStub.age.value,
|
||||
this.accountInfoFormStub.type.value,
|
||||
this.accountInfoFormStub.token.value,
|
||||
this.accountInfoFormStub.failedPinAttempts.value,
|
||||
@ -171,6 +194,7 @@ export class AccountDetailsComponent implements OnInit {
|
||||
this.accountInfoFormStub.businessCategory.value,
|
||||
this.accountInfoFormStub.userLocation.value,
|
||||
this.accountInfoFormStub.location.value,
|
||||
this.accountInfoFormStub.locationType.value,
|
||||
this.accountInfoFormStub.referrer.value,
|
||||
).then(res => console.log(res));
|
||||
this.submitted = false;
|
||||
@ -203,8 +227,4 @@ export class AccountDetailsComponent implements OnInit {
|
||||
console.log(res);
|
||||
});
|
||||
}
|
||||
|
||||
setDefault(formStub: any, name: string, value: string): void {
|
||||
formStub[name].value = value;
|
||||
}
|
||||
}
|
||||
|
@ -47,20 +47,20 @@ export class CreateAccountComponent implements OnInit {
|
||||
onSubmit(): void {
|
||||
this.submitted = true;
|
||||
if (this.createForm.invalid) { return; }
|
||||
this.userService.createAccount(
|
||||
this.createFormStub.accountType.value,
|
||||
this.createFormStub.idNumber.value,
|
||||
this.createFormStub.phoneNumber.value,
|
||||
this.createFormStub.givenName.value,
|
||||
this.createFormStub.surname.value,
|
||||
this.createFormStub.directoryEntry.value,
|
||||
this.createFormStub.location.value,
|
||||
this.createFormStub.gender.value,
|
||||
this.createFormStub.referrer.value,
|
||||
this.createFormStub.businessCategory.value,
|
||||
).pipe(first()).subscribe(res => {
|
||||
console.log(res);
|
||||
});
|
||||
// this.userService.createAccount(
|
||||
// this.createFormStub.accountType.value,
|
||||
// this.createFormStub.idNumber.value,
|
||||
// this.createFormStub.phoneNumber.value,
|
||||
// this.createFormStub.givenName.value,
|
||||
// this.createFormStub.surname.value,
|
||||
// this.createFormStub.directoryEntry.value,
|
||||
// this.createFormStub.location.value,
|
||||
// this.createFormStub.gender.value,
|
||||
// this.createFormStub.referrer.value,
|
||||
// this.createFormStub.businessCategory.value,
|
||||
// ).pipe(first()).subscribe(res => {
|
||||
// console.log(res);
|
||||
// });
|
||||
// this.router.navigateByUrl(`/accounts`);
|
||||
this.submitted = false;
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ export const environment = {
|
||||
publicKeysUrl: 'http://localhost:8000',
|
||||
cicCacheUrl: 'http://localhost:63313',
|
||||
cicScriptsUrl: 'http://localhost:9999',
|
||||
web3Provider: 'ws://localhost:8546',
|
||||
web3Provider: 'ws://localhost:63546',
|
||||
cicUssdUrl: 'http://localhost:63315',
|
||||
cicEthUrl: 'http://localhost:63314',
|
||||
contractAddress: '0x35Ef60C4624Eaf6AeEBeBec9Ddd3CBA6b24C4b17',
|
||||
|
@ -9,7 +9,7 @@ export const environment = {
|
||||
publicKeysUrl: 'http://localhost:8000',
|
||||
cicCacheUrl: 'http://localhost:63313',
|
||||
cicScriptsUrl: 'http://localhost:9999',
|
||||
web3Provider: 'ws://localhost:8546',
|
||||
web3Provider: 'ws://localhost:63546',
|
||||
cicUssdUrl: 'http://localhost:63315',
|
||||
cicEthUrl: 'http://localhost:63314',
|
||||
contractAddress: '0x35Ef60C4624Eaf6AeEBeBec9Ddd3CBA6b24C4b17',
|
||||
|
@ -1,15 +0,0 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/worker",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"webworker"
|
||||
],
|
||||
"types": []
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.worker.ts"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user