mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 00:33:44 +00:00
create base send details component
This commit is contained in:
@@ -0,0 +1,112 @@
|
|||||||
|
import { DatePipe } from "@angular/common";
|
||||||
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
|
import { FormBuilder, FormGroup, FormControl, Validators } from "@angular/forms";
|
||||||
|
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
|
|
||||||
|
import { SendFormConfig } from "../../abstractions/send-form-config.service";
|
||||||
|
import { SendFormContainer } from "../../send-form-container";
|
||||||
|
|
||||||
|
export interface BaseSendDetailsForm {
|
||||||
|
name: FormControl<string>;
|
||||||
|
selectedDeletionDatePreset: FormControl<string | number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value = hours
|
||||||
|
export enum DatePreset {
|
||||||
|
OneHour = 1,
|
||||||
|
OneDay = 24,
|
||||||
|
TwoDays = 48,
|
||||||
|
ThreeDays = 72,
|
||||||
|
SevenDays = 168,
|
||||||
|
FourteenDays = 336,
|
||||||
|
ThirtyDays = 720,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatePresetSelectOption {
|
||||||
|
name: string;
|
||||||
|
value: DatePreset | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "base-send-details-behavior",
|
||||||
|
template: "",
|
||||||
|
})
|
||||||
|
export class BaseSendDetailsComponent implements OnInit {
|
||||||
|
@Input() config: SendFormConfig;
|
||||||
|
@Input() originalSendView: SendView;
|
||||||
|
|
||||||
|
sendDetailsForm: FormGroup<BaseSendDetailsForm>;
|
||||||
|
customDeletionDateOption: DatePresetSelectOption | null = null;
|
||||||
|
datePresetOptions: DatePresetSelectOption[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected sendFormContainer: SendFormContainer,
|
||||||
|
protected formBuilder: FormBuilder,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected datePipe: DatePipe,
|
||||||
|
) {
|
||||||
|
// Initialize the form
|
||||||
|
this.sendDetailsForm = this.formBuilder.group({
|
||||||
|
name: new FormControl("", Validators.required),
|
||||||
|
selectedDeletionDatePreset: new FormControl(DatePreset.SevenDays || "", Validators.required),
|
||||||
|
}) as FormGroup<BaseSendDetailsForm>;
|
||||||
|
|
||||||
|
this.sendDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
|
||||||
|
this.sendFormContainer.patchSend((send) => {
|
||||||
|
return Object.assign(send, {
|
||||||
|
name: value.name,
|
||||||
|
deletionDate: new Date(this.formattedDeletionDate),
|
||||||
|
expirationDate: new Date(this.formattedDeletionDate),
|
||||||
|
} as SendView);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
this.setupDeletionDatePresets();
|
||||||
|
|
||||||
|
if (this.originalSendView) {
|
||||||
|
this.sendDetailsForm.patchValue({
|
||||||
|
name: this.originalSendView.name,
|
||||||
|
selectedDeletionDatePreset: this.originalSendView.deletionDate.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.originalSendView.deletionDate) {
|
||||||
|
this.customDeletionDateOption = {
|
||||||
|
name: this.datePipe.transform(this.originalSendView.deletionDate, "MM/dd/yyyy, hh:mm a"),
|
||||||
|
value: this.originalSendView.deletionDate.toString(),
|
||||||
|
};
|
||||||
|
this.datePresetOptions.unshift(this.customDeletionDateOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDeletionDatePresets() {
|
||||||
|
const defaultSelections: DatePresetSelectOption[] = [
|
||||||
|
{ name: this.i18nService.t("oneHour"), value: DatePreset.OneHour },
|
||||||
|
{ name: this.i18nService.t("oneDay"), value: DatePreset.OneDay },
|
||||||
|
{ name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays },
|
||||||
|
{ name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays },
|
||||||
|
{ name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays },
|
||||||
|
{ name: this.i18nService.t("days", "14"), value: DatePreset.FourteenDays },
|
||||||
|
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
|
||||||
|
];
|
||||||
|
|
||||||
|
this.datePresetOptions = defaultSelections;
|
||||||
|
}
|
||||||
|
|
||||||
|
get formattedDeletionDate(): string {
|
||||||
|
const now = new Date();
|
||||||
|
const selectedValue = this.sendDetailsForm.controls.selectedDeletionDatePreset.value;
|
||||||
|
|
||||||
|
if (typeof selectedValue === "string") {
|
||||||
|
return selectedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const milliseconds = now.setTime(now.getTime() + (selectedValue as number) * 60 * 60 * 1000);
|
||||||
|
return new Date(milliseconds).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<bit-section [formGroup]="sendDetailsForm">
|
||||||
|
<bit-section-header>
|
||||||
|
<h2 bitTypography="h5">{{ "sendDetails" | i18n }}</h2>
|
||||||
|
</bit-section-header>
|
||||||
|
|
||||||
|
<bit-card>
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>{{ "name" | i18n }}</bit-label>
|
||||||
|
<input bitInput type="text" formControlName="name" />
|
||||||
|
</bit-form-field>
|
||||||
|
|
||||||
|
<tools-send-text-details
|
||||||
|
*ngIf="config.sendType === TextSendType"
|
||||||
|
[config]="config"
|
||||||
|
[originalSendView]="originalSendView"
|
||||||
|
></tools-send-text-details>
|
||||||
|
|
||||||
|
<bit-form-field>
|
||||||
|
<bit-label>{{ "deletionDate" | i18n }}</bit-label>
|
||||||
|
<bit-select
|
||||||
|
id="deletionDate"
|
||||||
|
name="SelectedDeletionDatePreset"
|
||||||
|
formControlName="selectedDeletionDatePreset"
|
||||||
|
>
|
||||||
|
<bit-option
|
||||||
|
*ngFor="let o of datePresetOptions"
|
||||||
|
[value]="o.value"
|
||||||
|
[label]="o.name"
|
||||||
|
></bit-option>
|
||||||
|
</bit-select>
|
||||||
|
<bit-hint>{{ "deletionDateDescV2" | i18n }}</bit-hint>
|
||||||
|
</bit-form-field>
|
||||||
|
</bit-card>
|
||||||
|
</bit-section>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { CommonModule, DatePipe } from "@angular/common";
|
||||||
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
|
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
|
||||||
|
|
||||||
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||||
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
|
import {
|
||||||
|
SectionComponent,
|
||||||
|
SectionHeaderComponent,
|
||||||
|
TypographyModule,
|
||||||
|
CardComponent,
|
||||||
|
FormFieldModule,
|
||||||
|
IconButtonModule,
|
||||||
|
CheckboxModule,
|
||||||
|
SelectModule,
|
||||||
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { SendFormConfig } from "../../abstractions/send-form-config.service";
|
||||||
|
import { SendFormContainer } from "../../send-form-container";
|
||||||
|
|
||||||
|
import { BaseSendDetailsComponent } from "./base-send-details.component";
|
||||||
|
import { SendTextDetailsComponent } from "./send-text-details.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "tools-send-details",
|
||||||
|
templateUrl: "./send-details.component.html",
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
SectionComponent,
|
||||||
|
SectionHeaderComponent,
|
||||||
|
TypographyModule,
|
||||||
|
JslibModule,
|
||||||
|
CardComponent,
|
||||||
|
FormFieldModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
SendTextDetailsComponent,
|
||||||
|
IconButtonModule,
|
||||||
|
CheckboxModule,
|
||||||
|
CommonModule,
|
||||||
|
SelectModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class SendDetailsComponent extends BaseSendDetailsComponent implements OnInit {
|
||||||
|
@Input() config: SendFormConfig;
|
||||||
|
@Input() originalSendView: SendView;
|
||||||
|
|
||||||
|
FileSendType = SendType.File;
|
||||||
|
TextSendType = SendType.Text;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected sendFormContainer: SendFormContainer,
|
||||||
|
protected formBuilder: FormBuilder,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected datePipe: DatePipe,
|
||||||
|
) {
|
||||||
|
super(sendFormContainer, formBuilder, i18nService, datePipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit() {
|
||||||
|
await super.ngOnInit();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +1,9 @@
|
|||||||
<bit-section [formGroup]="sendTextDetailsForm">
|
<bit-form-field>
|
||||||
<bit-section-header>
|
|
||||||
<h2 bitTypography="h5">{{ "sendDetails" | i18n }}</h2>
|
|
||||||
</bit-section-header>
|
|
||||||
|
|
||||||
<bit-card>
|
|
||||||
<bit-form-field>
|
|
||||||
<bit-label>{{ "name" | i18n }}</bit-label>
|
|
||||||
<input bitInput type="text" formControlName="name" />
|
|
||||||
</bit-form-field>
|
|
||||||
|
|
||||||
<bit-form-field>
|
|
||||||
<bit-label>{{ "sendTypeTextToShare" | i18n }}</bit-label>
|
<bit-label>{{ "sendTypeTextToShare" | i18n }}</bit-label>
|
||||||
<textarea bitInput id="text" rows="6" formControlName="textToShare"></textarea>
|
<textarea bitInput id="text" rows="6" formControlName="textToShare"></textarea>
|
||||||
<bit-hint>{{ "sendTextDesc" | i18n }}</bit-hint>
|
<bit-hint>{{ "sendTextDesc" | i18n }}</bit-hint>
|
||||||
</bit-form-field>
|
</bit-form-field>
|
||||||
<bit-form-control>
|
<bit-form-control>
|
||||||
<input bitCheckbox type="checkbox" formControlName="hideTextByDefault" />
|
<input bitCheckbox type="checkbox" formControlName="hideTextByDefault" />
|
||||||
<bit-label>{{ "hideTextByDefault" | i18n }}</bit-label>
|
<bit-label>{{ "hideTextByDefault" | i18n }}</bit-label>
|
||||||
</bit-form-control>
|
</bit-form-control>
|
||||||
|
|
||||||
<bit-form-field>
|
|
||||||
<bit-label>{{ "deletionDate" | i18n }}</bit-label>
|
|
||||||
<bit-select
|
|
||||||
id="deletionDate"
|
|
||||||
name="SelectedDeletionDatePreset"
|
|
||||||
formControlName="selectedDeletionDatePreset"
|
|
||||||
>
|
|
||||||
<bit-option
|
|
||||||
*ngFor="let o of datePresetOptions"
|
|
||||||
[value]="o.value"
|
|
||||||
[label]="o.name"
|
|
||||||
></bit-option>
|
|
||||||
</bit-select>
|
|
||||||
<bit-hint>{{ "deletionDateDescV2" | i18n }}</bit-hint>
|
|
||||||
</bit-form-field>
|
|
||||||
</bit-card>
|
|
||||||
</bit-section>
|
|
||||||
|
|||||||
@@ -1,142 +1,15 @@
|
|||||||
import { CommonModule, DatePipe } from "@angular/common";
|
import { Component, Input } from "@angular/core";
|
||||||
import { Component, Input, OnInit } from "@angular/core";
|
|
||||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|
||||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
|
||||||
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
import {
|
|
||||||
CardComponent,
|
|
||||||
CheckboxModule,
|
|
||||||
FormFieldModule,
|
|
||||||
IconButtonModule,
|
|
||||||
SectionComponent,
|
|
||||||
SectionHeaderComponent,
|
|
||||||
SelectModule,
|
|
||||||
TypographyModule,
|
|
||||||
} from "@bitwarden/components";
|
|
||||||
|
|
||||||
import { SendFormConfig } from "../../abstractions/send-form-config.service";
|
import { SendFormConfig } from "../../abstractions/send-form-config.service";
|
||||||
import { SendFormContainer } from "../../send-form-container";
|
|
||||||
|
|
||||||
// Value = hours
|
|
||||||
enum DatePreset {
|
|
||||||
OneHour = 1,
|
|
||||||
OneDay = 24,
|
|
||||||
TwoDays = 48,
|
|
||||||
ThreeDays = 72,
|
|
||||||
SevenDays = 168,
|
|
||||||
FourteenDays = 336,
|
|
||||||
ThirtyDays = 720,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DatePresetSelectOption {
|
|
||||||
name: string;
|
|
||||||
value: DatePreset | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "tools-send-text-details",
|
selector: "tools-send-text-details",
|
||||||
templateUrl: "./send-text-details.component.html",
|
template: "send-text-details.component.html",
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
|
||||||
SectionComponent,
|
|
||||||
SectionHeaderComponent,
|
|
||||||
TypographyModule,
|
|
||||||
JslibModule,
|
|
||||||
CardComponent,
|
|
||||||
FormFieldModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
IconButtonModule,
|
|
||||||
CheckboxModule,
|
|
||||||
CommonModule,
|
|
||||||
SelectModule,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class SendTextDetailsComponent implements OnInit {
|
export class SendTextDetailsComponent {
|
||||||
@Input({ required: true })
|
@Input() config: SendFormConfig;
|
||||||
config: SendFormConfig;
|
@Input() originalSendView: SendView;
|
||||||
|
|
||||||
@Input()
|
|
||||||
originalSendView: SendView;
|
|
||||||
|
|
||||||
sendTextDetailsForm = this.formBuilder.group({
|
|
||||||
name: ["", [Validators.required]],
|
|
||||||
textToShare: [""],
|
|
||||||
hideTextByDefault: [false],
|
|
||||||
selectedDeletionDatePreset: [DatePreset.SevenDays || "", Validators.required],
|
|
||||||
});
|
|
||||||
|
|
||||||
customDeletionDateOption: DatePresetSelectOption | null = null;
|
|
||||||
datePresetOptions: DatePresetSelectOption[] = [];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private sendFormContainer: SendFormContainer,
|
|
||||||
private formBuilder: FormBuilder,
|
|
||||||
private i18nService: I18nService,
|
|
||||||
protected datePipe: DatePipe,
|
|
||||||
) {
|
|
||||||
this.sendFormContainer.registerChildForm("sendTextDetailsForm", this.sendTextDetailsForm);
|
|
||||||
|
|
||||||
this.sendTextDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
|
|
||||||
this.sendFormContainer.patchSend((send) => {
|
|
||||||
return Object.assign(send, {
|
|
||||||
name: value.name,
|
|
||||||
text: { text: value.textToShare, hidden: value.hideTextByDefault },
|
|
||||||
deletionDate: new Date(this.formattedDeletionDate),
|
|
||||||
expirationDate: new Date(this.formattedDeletionDate),
|
|
||||||
} as SendView);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async ngOnInit() {
|
|
||||||
this.setupDeletionDatePresets();
|
|
||||||
|
|
||||||
if (this.originalSendView) {
|
|
||||||
this.sendTextDetailsForm.patchValue({
|
|
||||||
name: this.originalSendView.name,
|
|
||||||
textToShare: this.originalSendView.text.text,
|
|
||||||
hideTextByDefault: this.originalSendView.text.hidden,
|
|
||||||
selectedDeletionDatePreset: this.originalSendView.deletionDate.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// If existing deletion date exists, calculate it once and store it
|
|
||||||
if (this.originalSendView.deletionDate) {
|
|
||||||
this.customDeletionDateOption = {
|
|
||||||
name: this.datePipe.transform(this.originalSendView.deletionDate, "MM/dd/yyyy, hh:mm a"),
|
|
||||||
value: this.originalSendView.deletionDate.toString(),
|
|
||||||
};
|
|
||||||
this.datePresetOptions.unshift(this.customDeletionDateOption);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setupDeletionDatePresets() {
|
|
||||||
const defaultSelections: DatePresetSelectOption[] = [
|
|
||||||
{ name: this.i18nService.t("oneHour"), value: DatePreset.OneHour },
|
|
||||||
{ name: this.i18nService.t("oneDay"), value: DatePreset.OneDay },
|
|
||||||
{ name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays },
|
|
||||||
{ name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays },
|
|
||||||
{ name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays },
|
|
||||||
{ name: this.i18nService.t("days", "14"), value: DatePreset.FourteenDays },
|
|
||||||
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
|
|
||||||
];
|
|
||||||
|
|
||||||
this.datePresetOptions = defaultSelections;
|
|
||||||
}
|
|
||||||
|
|
||||||
get formattedDeletionDate(): string {
|
|
||||||
const now = new Date();
|
|
||||||
const selectedValue = this.sendTextDetailsForm.controls.selectedDeletionDatePreset.value;
|
|
||||||
|
|
||||||
// If existing deletion date is selected, return it as is
|
|
||||||
if (typeof selectedValue === "string") {
|
|
||||||
return selectedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const milliseconds = now.setTime(now.getTime() + (selectedValue as number) * 60 * 60 * 1000);
|
|
||||||
return new Date(milliseconds).toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
<form [id]="formId" [formGroup]="sendForm" [bitSubmit]="submit">
|
<form [id]="formId" [formGroup]="sendForm" [bitSubmit]="submit">
|
||||||
<!-- TODO: Should we show a loading spinner here? Or emit a ready event for the container to handle loading state -->
|
|
||||||
<ng-container *ngIf="!loading">
|
<ng-container *ngIf="!loading">
|
||||||
<tools-send-text-details
|
<tools-send-details
|
||||||
*ngIf="config.sendType === SendType.Text"
|
|
||||||
[config]="config"
|
[config]="config"
|
||||||
[originalSendView]="originalSendView"
|
[originalSendView]="originalSendView"
|
||||||
></tools-send-text-details>
|
></tools-send-details>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { SendFormConfig } from "../abstractions/send-form-config.service";
|
|||||||
import { SendFormService } from "../abstractions/send-form.service";
|
import { SendFormService } from "../abstractions/send-form.service";
|
||||||
import { SendForm, SendFormContainer } from "../send-form-container";
|
import { SendForm, SendFormContainer } from "../send-form-container";
|
||||||
|
|
||||||
import { SendTextDetailsComponent } from "./send-details/send-text-details.component";
|
import { SendDetailsComponent } from "./send-details/send-details.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "tools-send-form",
|
selector: "tools-send-form",
|
||||||
@@ -57,7 +57,7 @@ import { SendTextDetailsComponent } from "./send-details/send-text-details.compo
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
SelectModule,
|
SelectModule,
|
||||||
NgIf,
|
NgIf,
|
||||||
SendTextDetailsComponent,
|
SendDetailsComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, SendFormContainer {
|
export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, SendFormContainer {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
|
|
||||||
import { SendFormConfig } from "./abstractions/send-form-config.service";
|
import { SendFormConfig } from "./abstractions/send-form-config.service";
|
||||||
import { SendTextDetailsComponent } from "./components/send-details/send-text-details.component";
|
import { SendDetailsComponent } from "./components/send-details/send-details.component";
|
||||||
/**
|
/**
|
||||||
* The complete form for a send. Includes all the sub-forms from their respective section components.
|
* The complete form for a send. Includes all the sub-forms from their respective section components.
|
||||||
* TODO: Add additional form sections as they are implemented.
|
* TODO: Add additional form sections as they are implemented.
|
||||||
*/
|
*/
|
||||||
export type SendForm = {
|
export type SendForm = {
|
||||||
sendTextDetailsForm?: SendTextDetailsComponent["sendTextDetailsForm"];
|
sendDetailsForm?: SendDetailsComponent["sendDetailsForm"];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user