error management
This commit is contained in:
+7
-1
@@ -28,6 +28,7 @@ public class PasswordModelValidator implements Validator {
|
||||
|
||||
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_WHITESPACE = "password.rule.whitespace";
|
||||
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_LENGTH = "password.rule.length";
|
||||
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_LOWERCASE = "password.rule.lowercase";
|
||||
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_UPPERCASE = "password.rule.uppercase";
|
||||
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_DIGIT = "password.rule.digit";
|
||||
public static final String SYSTEM_PROPERTY_PASSWORD_RULE_SPECIAL = "password.rule.special";
|
||||
@@ -52,6 +53,11 @@ public class PasswordModelValidator implements Validator {
|
||||
rules.add(new LengthRule(length, 4096));
|
||||
}
|
||||
|
||||
int lowercase = systemPropertyManager.getInteger(SYSTEM_PROPERTY_PASSWORD_RULE_LOWERCASE, 1);
|
||||
if (lowercase > 0) {
|
||||
rules.add(new CharacterRule(EnglishCharacterData.LowerCase, lowercase));
|
||||
}
|
||||
|
||||
int uppercase = systemPropertyManager.getInteger(SYSTEM_PROPERTY_PASSWORD_RULE_UPPERCASE, 1);
|
||||
if (uppercase > 0) {
|
||||
rules.add(new CharacterRule(EnglishCharacterData.UpperCase, uppercase));
|
||||
@@ -73,7 +79,7 @@ public class PasswordModelValidator implements Validator {
|
||||
|
||||
if (!result.isValid()) {
|
||||
for (RuleResultDetail ruleResultDetail : result.getDetails()) {
|
||||
errors.rejectValue("password", ruleResultDetail.getErrorCode());
|
||||
errors.rejectValue("password", ruleResultDetail.getErrorCode(), ruleResultDetail.getValues(), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -32,11 +32,11 @@ public class TurnoverValidator implements Validator {
|
||||
}
|
||||
|
||||
if (turnover.getPrice() < 0) {
|
||||
errors.rejectValue("price", "POSITIVE_VALUE");
|
||||
errors.rejectValue("price", "MIN");
|
||||
}
|
||||
|
||||
if (turnover.getGiftcardPrice() != null && turnover.getGiftcardPrice() < 0) {
|
||||
errors.rejectValue("giftcardPrice", "POSITIVE_VALUE");
|
||||
errors.rejectValue("giftcardPrice", "MIN");
|
||||
} else if (turnover.getGiftcardPrice() != null && turnover.getGiftcardPrice() > turnover.getPrice()) {
|
||||
errors.rejectValue("giftcardPrice", "GREATER_THAN_PRICE");
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { PageNotFound } from './pages/notfound/notfound.page';
|
||||
import { PageProfile } from './pages/profile/profile.page';
|
||||
import { PageUnavailable } from './pages/unavailable/unavailable.page';
|
||||
import { UiMain } from './ui/main/main.ui';
|
||||
import { I18nEmptyPipe, I18nPipe } from './utils/i18n.pipe';
|
||||
import { ErrorCodePipe, I18nEmptyPipe, I18nPipe } from './utils/i18n.pipe';
|
||||
import { MomentPipe } from './utils/moment.pipe';
|
||||
|
||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||
@@ -66,32 +66,33 @@ export class XhrInterceptor implements HttpInterceptor {
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
AutofocusDirective,
|
||||
ConfirmDialog,
|
||||
ErrorCodePipe,
|
||||
I18nPipe,
|
||||
I18nEmptyPipe,
|
||||
MomentPipe,
|
||||
AppComponent,
|
||||
PageTurnovers,
|
||||
PageTurnoversManage,
|
||||
PageTurnover,
|
||||
PageLogin,
|
||||
PageManagement,
|
||||
PageNotFound,
|
||||
PagePassword,
|
||||
PageProfile,
|
||||
PageTurnover,
|
||||
PageTurnovers,
|
||||
PageTurnoversManage,
|
||||
PageUnavailable,
|
||||
PageUsers,
|
||||
PageUserCreate,
|
||||
UiMain,
|
||||
UiTurnovers,
|
||||
ConfirmDialog
|
||||
UiTurnovers
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
MaterialModule,
|
||||
FormsModule,
|
||||
MaterialModule,
|
||||
ReactiveFormsModule,
|
||||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production, registrationStrategy: 'registerWhenStable:30000' }),
|
||||
],
|
||||
|
||||
@@ -7,21 +7,21 @@
|
||||
<mat-label>{{'password.old' | i18n}}</mat-label>
|
||||
<input matInput formControlName="old" type="password">
|
||||
<mat-error *ngFor="let error of passwordForm.get('old').errors | keyvalue">
|
||||
{{'password.error.' + error.key | i18n}}
|
||||
{{'password.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>{{'password.new' | i18n}}</mat-label>
|
||||
<input matInput formControlName="password" type="password">
|
||||
<mat-error *ngFor="let error of passwordForm.get('password').errors | keyvalue">
|
||||
{{'password.error.' + error.key | i18n}}
|
||||
{{'password.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>{{'password.repeat' | i18n}}</mat-label>
|
||||
<input matInput formControlName="password2" type="password">
|
||||
<mat-error *ngFor="let error of passwordForm.get('password2').errors | keyvalue">
|
||||
{{'password.error.' + error.key | i18n}}
|
||||
{{'password.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
|
||||
@@ -53,9 +53,9 @@ export class PagePassword implements OnInit, OnDestroy {
|
||||
this.working = false;
|
||||
if (error.status == 409) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
for (let errorObject of error.error) {
|
||||
errors[errorObject.field] = errors[errorObject.field] || {};
|
||||
errors[errorObject.field][errorObject.code] = errorObject.arguments || true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
|
||||
@@ -50,14 +50,14 @@
|
||||
<mat-label>{{'password.new' | i18n}}</mat-label>
|
||||
<input matInput formControlName="password" type="password">
|
||||
<mat-error *ngFor="let error of passwordForm.get('password').errors | keyvalue">
|
||||
{{'password.error.' + error.key | i18n}}
|
||||
{{'password.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>{{'password.repeat' | i18n}}</mat-label>
|
||||
<input matInput formControlName="password2" type="password">
|
||||
<mat-error *ngFor="let error of passwordForm.get('password2').errors | keyvalue">
|
||||
{{'password.error.' + error.key | i18n}}
|
||||
{{'password.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
|
||||
@@ -108,10 +108,10 @@ export class PageProfile implements OnInit, OnDestroy {
|
||||
this.working = false;
|
||||
if (error.status == 422) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
}
|
||||
for (let errorObject of error.error) {
|
||||
errors[errorObject.field] = errors[errorObject.field] || {};
|
||||
errors[errorObject.field][errorObject.code] = errorObject.arguments || true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
this.profileForm.get(code).setErrors(errors[code]);
|
||||
@@ -138,10 +138,10 @@ export class PageProfile implements OnInit, OnDestroy {
|
||||
this.working = false;
|
||||
if (error.status == 409) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
}
|
||||
for (let errorObject of error.error) {
|
||||
errors[errorObject.field] = errors[errorObject.field] || {};
|
||||
errors[errorObject.field][errorObject.code] = errorObject.arguments || true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
this.passwordForm.get(code).setErrors(errors[code]);
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<mat-label>{{'turnover.customer' | i18n}}</mat-label>
|
||||
<input matInput formControlName="customer" type="text" [required]="true">
|
||||
<mat-error *ngFor="let error of form.get('customer').errors | keyvalue">
|
||||
{{'turnover.customer.error.' + error.key | i18n}}
|
||||
{{'turnover.customer.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<mat-label>{{'turnover.motif' | i18n}}</mat-label>
|
||||
<input matInput formControlName="motif" type="text" [required]="true">
|
||||
<mat-error *ngFor="let error of form.get('motif').errors | keyvalue">
|
||||
{{'turnover.motif.error.' + error.key | i18n}}
|
||||
{{'turnover.motif.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<input matInput formControlName="price" type="number" min="0" step="0.01" [required]="true">
|
||||
<span matTextSuffix>{{'turnover.price.suffix' | i18n}}</span>
|
||||
<mat-error *ngFor="let error of form.get('price').errors | keyvalue">
|
||||
{{'turnover.price.error.' + error.key | i18n}}
|
||||
{{'turnover.price.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<mat-label>{{'turnover.giftcard.number' | i18n}}</mat-label>
|
||||
<input matInput formControlName="giftcardNumber" type="text">
|
||||
<mat-error *ngFor="let error of form.get('giftcardNumber').errors | keyvalue">
|
||||
{{'turnover.giftcard.number.error.' + error.key | i18n}}
|
||||
{{'turnover.giftcard.number.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<input matInput formControlName="giftcardPrice" type="number" min="0" step="0.01">
|
||||
<span matTextSuffix>{{'turnover.price.suffix' | i18n}}</span>
|
||||
<mat-error *ngFor="let error of form.get('giftcardPrice').errors | keyvalue">
|
||||
{{'turnover.giftcard.price.error.' + error.key | i18n}}
|
||||
{{'turnover.giftcard.price.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<input matInput formControlName="timeInvestment" type="number" min="0" step="0.1">
|
||||
<span matTextSuffix>{{'turnover.timeInvestment.suffix' | i18n}}</span>
|
||||
<mat-error *ngFor="let error of form.get('timeInvestment').errors | keyvalue">
|
||||
{{'turnover.timeInvestment.error.' + error.key | i18n}}
|
||||
{{'turnover.timeInvestment.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<mat-label>{{'turnover.remark' | i18n}}</mat-label>
|
||||
<textarea matAutosize matAutosizeMinRows="3" matInput formControlName="remark"></textarea>
|
||||
<mat-error *ngFor="let error of form.get('remark').errors | keyvalue">
|
||||
{{'turnover.remark.error.' + error.key | i18n}}
|
||||
{{'turnover.remark.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<mat-label>{{'turnover.materialConsumption' | i18n}}</mat-label>
|
||||
<textarea matAutosize matAutosizeMinRows="3" matInput formControlName="materialConsumption"></textarea>
|
||||
<mat-error *ngFor="let error of form.get('materialConsumption').errors | keyvalue">
|
||||
{{'turnover.materialConsumption.error.' + error.key | i18n}}
|
||||
{{'turnover.materialConsumption.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
@@ -137,10 +137,10 @@ export class PageTurnover implements OnInit {
|
||||
this.working = false;
|
||||
if (error.status == 422) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
}
|
||||
for (let errorObject of error.error) {
|
||||
errors[errorObject.field] = errors[errorObject.field] || {};
|
||||
errors[errorObject.field][errorObject.code] = errorObject.arguments || true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
this.form.get(code).setErrors(errors[code]);
|
||||
@@ -188,10 +188,10 @@ export class PageTurnover implements OnInit {
|
||||
}
|
||||
if (error.status == 409) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
}
|
||||
for (let errorObject of error.error) {
|
||||
errors[errorObject.field] = errors[errorObject.field] || {};
|
||||
errors[errorObject.field][errorObject.code] = errorObject.arguments || true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
this.form.get(code).setErrors(errors[code]);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<mat-label>{{'profile.username' | i18n}}</mat-label>
|
||||
<input matInput formControlName="username" type="text" [required]="true">
|
||||
<mat-error *ngFor="let error of form.get('username').errors | keyvalue">
|
||||
{{'user.error.' + error.key | i18n}}
|
||||
{{'user.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<mat-label>{{'profile.name' | i18n}}</mat-label>
|
||||
<input matInput formControlName="name" type="text">
|
||||
<mat-error *ngFor="let error of form.get('name').errors | keyvalue">
|
||||
{{'user.error.' + error.key | i18n}}
|
||||
{{'user.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<mat-label>{{'profile.email' | i18n}}</mat-label>
|
||||
<input matInput formControlName="email" type="email">
|
||||
<mat-error *ngFor="let error of form.get('email').errors | keyvalue">
|
||||
{{'user.error.' + error.key | i18n}}
|
||||
{{'user.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<mat-label>{{'user.password' | i18n}}</mat-label>
|
||||
<input matInput formControlName="password" type="password">
|
||||
<mat-error *ngFor="let error of form.get('password').errors | keyvalue">
|
||||
{{'password.error.' + error.key | i18n}}
|
||||
{{'password.error.' + (error.key | errorCode) | i18n:error.value}}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
@@ -52,12 +52,13 @@ export class PageUserCreate implements OnInit {
|
||||
this.working = false;
|
||||
if (error.status == 409) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
for (let errorObject of error.error) {
|
||||
errors[errorObject.field] = errors[errorObject.field] || {};
|
||||
errors[errorObject.field][errorObject.code] = errorObject.arguments || true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
console.log(code, errors[code]);
|
||||
this.form.get(code).setErrors(errors[code]);
|
||||
}
|
||||
}
|
||||
@@ -68,9 +69,9 @@ export class PageUserCreate implements OnInit {
|
||||
this.working = false;
|
||||
if (error.status == 409) {
|
||||
let errors = {};
|
||||
for (let code of error.error) {
|
||||
errors[code.field] = errors[code.field] || {};
|
||||
errors[code.field][code.code] = true;
|
||||
for (let errorObject of error.error) {
|
||||
errors[errorObject.field] = errors[errorObject.field] || {};
|
||||
errors[errorObject.field][errorObject.code] = errorObject.arguments || true;
|
||||
}
|
||||
|
||||
for (let code in errors) {
|
||||
|
||||
@@ -22,8 +22,8 @@ export class UiMain {
|
||||
opened: boolean = true;
|
||||
darkTheme: boolean = false;
|
||||
title = 'buntspecht';
|
||||
currentLocale: String;
|
||||
datetimeformat: String;
|
||||
currentLocale: string;
|
||||
datetimeformat: string;
|
||||
locales;
|
||||
authenticated: boolean = false;
|
||||
username: string = "";
|
||||
|
||||
@@ -12,7 +12,10 @@ export class I18nPipe implements PipeTransform {
|
||||
constructor(private i18n: I18nService) {
|
||||
}
|
||||
|
||||
transform(value: String, ...args: any[]): String {
|
||||
transform(value: string, ...args: any[]): string {
|
||||
if (args.length === 1 && Array.isArray(args[0])) {
|
||||
args = args[0];
|
||||
}
|
||||
return this.i18n.get(value, args);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +28,17 @@ export class I18nEmptyPipe implements PipeTransform {
|
||||
constructor(private i18n: I18nService) {
|
||||
}
|
||||
|
||||
transform(value: String, ...args: any[]): String {
|
||||
transform(value: string, ...args: any[]): string {
|
||||
return this.i18n.getEmpty(value, args);
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({
|
||||
name: 'errorCode'
|
||||
})
|
||||
export class ErrorCodePipe implements PipeTransform {
|
||||
|
||||
transform(value: string): string {
|
||||
return value && value.toUpperCase().replaceAll('-', '_') || value;
|
||||
}
|
||||
}
|
||||
@@ -63,12 +63,12 @@
|
||||
".": "Passwort ändern",
|
||||
"error": {
|
||||
"ILLEGAL_WHITESPACE": "Bitte keine Leerzeichen verwenden.",
|
||||
"INSUFFICIENT_DIGIT": "Bitte mindestens eine Zahl eingeben.",
|
||||
"INSUFFICIENT_LOWERCASE": "Bitte mindestens einen Kleinbuchstaben eingeben.",
|
||||
"INSUFFICIENT_SPECIAL": "Bitte mindestens ein Sonderzeichen eingeben.",
|
||||
"INSUFFICIENT_UPPERCASE": "Bitte mindestens einen Großbuchstaben eingeben.",
|
||||
"INSUFFICIENT_DIGIT": "Bitte mindestens {0} Zahl/en eingeben.",
|
||||
"INSUFFICIENT_LOWERCASE": "Bitte mindestens {0} Kleinbuchstaben eingeben.",
|
||||
"INSUFFICIENT_SPECIAL": "Bitte mindestens {0} Sonderzeichen eingeben.",
|
||||
"INSUFFICIENT_UPPERCASE": "Bitte mindestens {0} Großbuchstaben eingeben.",
|
||||
"NOT_MATCH": "Passwörter stimmen nicht überein.",
|
||||
"TOO_SHORT": "Bitte ein längeres Passwort wählen.",
|
||||
"TOO_SHORT": "Bitte Mindestens {0} Zeichen verwenden.",
|
||||
"UNAUTHORIZED": "Falsches Passwort"
|
||||
},
|
||||
"old": "Altes Password",
|
||||
@@ -108,7 +108,7 @@
|
||||
"customer": {
|
||||
".": "Kunde",
|
||||
"error": {
|
||||
"required": "Angabe von Kunde erforderlich"
|
||||
"REQUIRED": "Angabe von Kunde erforderlich"
|
||||
}
|
||||
},
|
||||
"dueDate": {
|
||||
@@ -122,14 +122,13 @@
|
||||
"giftcard": {
|
||||
".": "Gutschein",
|
||||
"number": {
|
||||
".": "Gutscheinnummer",
|
||||
"error": ""
|
||||
".": "Gutscheinnummer"
|
||||
},
|
||||
"price": {
|
||||
".": "Gutscheinwert",
|
||||
"error": {
|
||||
"GREATER_THAN_PRICE": "Gutscheinwert übersteigt Preis",
|
||||
"min": "Üngültiger Wert"
|
||||
"MIN": "Üngültiger Wert"
|
||||
}
|
||||
},
|
||||
"priceUsage": "Gutscheinanteil"
|
||||
@@ -139,14 +138,14 @@
|
||||
"motif": {
|
||||
".": "Motiv",
|
||||
"error": {
|
||||
"required": "Angabe von Motiv erforderlich"
|
||||
"REQUIRED": "Angabe von Motiv erforderlich"
|
||||
}
|
||||
},
|
||||
"price": {
|
||||
".": "Preis",
|
||||
"error": {
|
||||
"min": "Üngültiger Wert",
|
||||
"required": "Angabe des Preises erforderlich"
|
||||
"MIN": "Üngültiger Wert",
|
||||
"REQUIRED": "Angabe des Preises erforderlich"
|
||||
},
|
||||
"suffix": "€",
|
||||
"total": "Umsatz: {0} €"
|
||||
@@ -156,7 +155,7 @@
|
||||
"timeInvestment": {
|
||||
".": "Zeiteinsatz",
|
||||
"error": {
|
||||
"min": "Üngültiger Wert"
|
||||
"MIN": "Üngültiger Wert"
|
||||
},
|
||||
"suffix": "Std.",
|
||||
"total": "Zeiteinsatz: {0} Std."
|
||||
@@ -199,7 +198,8 @@
|
||||
"create": "Neuen User erstellen",
|
||||
"delete": "User löschen",
|
||||
"error": {
|
||||
"ALREADY_EXISTS": "User existiert bereits"
|
||||
"ALREADY_EXISTS": "User existiert bereits",
|
||||
"REQUIRED": "Angabe eines Usernames erforderlich"
|
||||
},
|
||||
"manage": "Verwalten",
|
||||
"password": "Passwort",
|
||||
|
||||
Reference in New Issue
Block a user