Replace hardcoded notification strings with standardized constants
- Replace literal strings with notification constants in ContactsView.vue: * "Got an error sending the invite." → NOTIFY_INVITE_ERROR.message * "Could not set visibility on the server." → NOTIFY_VISIBILITY_ERROR.message * "Unconfirmed Hours" → NOTIFY_UNCONFIRMED_HOURS.title - Remove unused NOTIFY_REGISTER_PROCESSING import - Remove unused NOTIFICATION_TIMEOUTS constant in ShareMyContactInfoView.vue - Fix unused parameter warnings in danger() and warning() methods - Resolve all notification-related linting errors
This commit is contained in:
@@ -211,7 +211,7 @@ interface DecryptedMember {
|
|||||||
})
|
})
|
||||||
export default class MembersList extends Vue {
|
export default class MembersList extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ export default class MembersList extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
@@ -505,10 +505,7 @@ export default class MembersList extends Vue {
|
|||||||
if (err instanceof Error && err.message?.indexOf("already exists") > -1) {
|
if (err instanceof Error && err.message?.indexOf("already exists") > -1) {
|
||||||
message = "This person is already in your contact list.";
|
message = "This person is already in your contact list.";
|
||||||
}
|
}
|
||||||
this.notify.error(
|
this.notify.error(message, TIMEOUTS.LONG);
|
||||||
message,
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default class TopMessage extends Vue {
|
|||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ultra-concise cached settings loading - replaces 50+ lines of logic!
|
// Ultra-concise cached settings loading - replaces 50+ lines of logic!
|
||||||
const settings = await this.$accountSettings(undefined, {
|
const settings = await this.$accountSettings(undefined, {
|
||||||
@@ -62,10 +62,7 @@ export default class TopMessage extends Vue {
|
|||||||
this.message = "You are using prod, user " + didPrefix;
|
this.message = "You are using prod, user " + didPrefix;
|
||||||
}
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
this.notify.error(
|
this.notify.error(JSON.stringify(err), TIMEOUTS.MODAL);
|
||||||
JSON.stringify(err),
|
|
||||||
TIMEOUTS.MODAL,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||||
|
|
||||||
|
|
||||||
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "../db/tables/settings";
|
||||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ export const NOTIFY_VISIBILITY_REFRESHED = {
|
|||||||
// ContactsView.vue specific constants
|
// ContactsView.vue specific constants
|
||||||
export const NOTIFY_BLANK_INVITE = {
|
export const NOTIFY_BLANK_INVITE = {
|
||||||
title: "Blank Invite",
|
title: "Blank Invite",
|
||||||
message: "The invite was not included, which can happen when your iOS device cuts off the link. Try pasting the full link into a browser.",
|
message:
|
||||||
|
"The invite was not included, which can happen when your iOS device cuts off the link. Try pasting the full link into a browser.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NOTIFY_INVITE_REGISTRATION_SUCCESS = {
|
export const NOTIFY_INVITE_REGISTRATION_SUCCESS = {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export default class ClaimAddRawView extends Vue {
|
|||||||
*/
|
*/
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
await this.initializeSettings();
|
await this.initializeSettings();
|
||||||
await this.loadClaimData();
|
await this.loadClaimData();
|
||||||
}
|
}
|
||||||
@@ -203,10 +203,7 @@ export default class ClaimAddRawView extends Vue {
|
|||||||
"Error retrieving claim: " + errorStringForLog(error),
|
"Error retrieving claim: " + errorStringForLog(error),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
this.notify.error(
|
this.notify.error("Got an error retrieving claim data.", TIMEOUTS.STANDARD);
|
||||||
"Got an error retrieving claim data.",
|
|
||||||
TIMEOUTS.STANDARD,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,10 +223,7 @@ export default class ClaimAddRawView extends Vue {
|
|||||||
this.axios,
|
this.axios,
|
||||||
);
|
);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.notify.success(
|
this.notify.success("Claim submitted.", TIMEOUTS.LONG);
|
||||||
"Claim submitted.",
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
logger.error("Got error submitting the claim:", result);
|
logger.error("Got error submitting the claim:", result);
|
||||||
this.notify.error(
|
this.notify.error(
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export default class ContactGiftingView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ export default class ContactImportView extends Vue {
|
|||||||
*/
|
*/
|
||||||
async created() {
|
async created() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
await this.initializeSettings();
|
await this.initializeSettings();
|
||||||
await this.processQueryParams();
|
await this.processQueryParams();
|
||||||
await this.processJwtFromPath();
|
await this.processJwtFromPath();
|
||||||
|
|||||||
@@ -305,7 +305,6 @@ import {
|
|||||||
NOTIFY_INVITE_ERROR,
|
NOTIFY_INVITE_ERROR,
|
||||||
NOTIFY_ONBOARDING_CONFIRM,
|
NOTIFY_ONBOARDING_CONFIRM,
|
||||||
NOTIFY_REGISTER_NOT_AVAILABLE,
|
NOTIFY_REGISTER_NOT_AVAILABLE,
|
||||||
NOTIFY_REGISTER_PROCESSING,
|
|
||||||
NOTIFY_REGISTER_PERSON_SUCCESS,
|
NOTIFY_REGISTER_PERSON_SUCCESS,
|
||||||
NOTIFY_REGISTER_PERSON_ERROR,
|
NOTIFY_REGISTER_PERSON_ERROR,
|
||||||
NOTIFY_VISIBILITY_ERROR,
|
NOTIFY_VISIBILITY_ERROR,
|
||||||
@@ -390,10 +389,7 @@ export default class ContactsView extends Vue {
|
|||||||
|
|
||||||
// Methods for template simplification
|
// Methods for template simplification
|
||||||
showNotRegisteredWarning(): void {
|
showNotRegisteredWarning(): void {
|
||||||
this.notify.warning(
|
this.notify.warning(NOTIFY_REGISTER_NOT_AVAILABLE.message, TIMEOUTS.LONG);
|
||||||
NOTIFY_REGISTER_NOT_AVAILABLE.message,
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleAllContacts(): void {
|
toggleAllContacts(): void {
|
||||||
@@ -434,7 +430,7 @@ export default class ContactsView extends Vue {
|
|||||||
|
|
||||||
public async created() {
|
public async created() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
const settingsRow = await this.$getSettingsRow([
|
const settingsRow = await this.$getSettingsRow([
|
||||||
"activeDid",
|
"activeDid",
|
||||||
"apiServer",
|
"apiServer",
|
||||||
@@ -490,10 +486,7 @@ export default class ContactsView extends Vue {
|
|||||||
const importedInviteJwt = this.$route.query["inviteJwt"] as string;
|
const importedInviteJwt = this.$route.query["inviteJwt"] as string;
|
||||||
if (importedInviteJwt === "") {
|
if (importedInviteJwt === "") {
|
||||||
// this happens when a platform (eg iOS) doesn't include anything after the "=" in a shared link.
|
// this happens when a platform (eg iOS) doesn't include anything after the "=" in a shared link.
|
||||||
this.notify.error(
|
this.notify.error(NOTIFY_BLANK_INVITE.message, TIMEOUTS.VERY_LONG);
|
||||||
NOTIFY_BLANK_INVITE.message,
|
|
||||||
TIMEOUTS.VERY_LONG,
|
|
||||||
);
|
|
||||||
} else if (importedInviteJwt) {
|
} else if (importedInviteJwt) {
|
||||||
// make sure user is created
|
// make sure user is created
|
||||||
if (!this.activeDid) {
|
if (!this.activeDid) {
|
||||||
@@ -560,7 +553,7 @@ export default class ContactsView extends Vue {
|
|||||||
response?: { data?: { error?: { message?: string } } };
|
response?: { data?: { error?: { message?: string } } };
|
||||||
message?: string;
|
message?: string;
|
||||||
};
|
};
|
||||||
let message: string = "Got an error sending the invite.";
|
let message: string = NOTIFY_INVITE_ERROR.message;
|
||||||
if (err.response && err.response.data && err.response.data.error) {
|
if (err.response && err.response.data && err.response.data.error) {
|
||||||
if (err.response.data.error.message) {
|
if (err.response.data.error.message) {
|
||||||
message = err.response.data.error.message;
|
message = err.response.data.error.message;
|
||||||
@@ -573,10 +566,7 @@ export default class ContactsView extends Vue {
|
|||||||
} else if (typeof err.message === "string") {
|
} else if (typeof err.message === "string") {
|
||||||
message = err.message;
|
message = err.message;
|
||||||
}
|
}
|
||||||
this.notify.error(
|
this.notify.error(message, TIMEOUTS.MODAL);
|
||||||
message,
|
|
||||||
TIMEOUTS.MODAL,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// if we're here, they haven't redirected anywhere, so we'll redirect here without a query parameter
|
// if we're here, they haven't redirected anywhere, so we'll redirect here without a query parameter
|
||||||
this.$router.push({ path: "/contacts" });
|
this.$router.push({ path: "/contacts" });
|
||||||
@@ -587,14 +577,10 @@ export default class ContactsView extends Vue {
|
|||||||
return (contactName || AppString.NO_CONTACT_NAME).replace(/\s/g, "\u00A0");
|
return (contactName || AppString.NO_CONTACT_NAME).replace(/\s/g, "\u00A0");
|
||||||
}
|
}
|
||||||
|
|
||||||
private danger(message: string, title: string = "Error", timeout = 5000) {
|
private danger(message: string, _title: string = "Error", timeout = 5000) {
|
||||||
this.notify.error(message, timeout);
|
this.notify.error(message, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private warning(message: string, title: string = "Error", timeout = 5000) {
|
|
||||||
this.notify.warning(message, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private showOnboardingInfo() {
|
private showOnboardingInfo() {
|
||||||
this.notify.confirm(
|
this.notify.confirm(
|
||||||
NOTIFY_ONBOARDING_CONFIRM.message,
|
NOTIFY_ONBOARDING_CONFIRM.message,
|
||||||
@@ -703,10 +689,7 @@ export default class ContactsView extends Vue {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
const fullError = "Error loading gives: " + errorStringForLog(error);
|
const fullError = "Error loading gives: " + errorStringForLog(error);
|
||||||
logConsoleAndDb(fullError, true);
|
logConsoleAndDb(fullError, true);
|
||||||
this.notify.error(
|
this.notify.error("Got an error loading your gives.", TIMEOUTS.STANDARD);
|
||||||
"Got an error loading your gives.",
|
|
||||||
TIMEOUTS.STANDARD,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -929,10 +912,7 @@ export default class ContactsView extends Vue {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.notify.success(
|
this.notify.success(addedMessage, TIMEOUTS.STANDARD);
|
||||||
addedMessage,
|
|
||||||
TIMEOUTS.STANDARD,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const fullError =
|
const fullError =
|
||||||
@@ -1002,10 +982,7 @@ export default class ContactsView extends Vue {
|
|||||||
userMessage = error as string;
|
userMessage = error as string;
|
||||||
}
|
}
|
||||||
// Now set that error for the user to see.
|
// Now set that error for the user to see.
|
||||||
this.notify.error(
|
this.notify.error(userMessage, TIMEOUTS.MODAL);
|
||||||
userMessage,
|
|
||||||
TIMEOUTS.MODAL,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1037,11 +1014,8 @@ export default class ContactsView extends Vue {
|
|||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
const message =
|
const message =
|
||||||
(result.error as string) || "Could not set visibility on the server.";
|
(result.error as string) || NOTIFY_VISIBILITY_ERROR.message;
|
||||||
this.notify.error(
|
this.notify.error(message, TIMEOUTS.LONG);
|
||||||
message,
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1067,7 +1041,7 @@ export default class ContactsView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "modal",
|
group: "modal",
|
||||||
type: "confirm",
|
type: "confirm",
|
||||||
title: "Unconfirmed Hours",
|
title: NOTIFY_UNCONFIRMED_HOURS.title,
|
||||||
text: message,
|
text: message,
|
||||||
onNo: async () => {
|
onNo: async () => {
|
||||||
this.showGiftedDialog(giverDid, recipientDid);
|
this.showGiftedDialog(giverDid, recipientDid);
|
||||||
@@ -1223,10 +1197,7 @@ export default class ContactsView extends Vue {
|
|||||||
useClipboard()
|
useClipboard()
|
||||||
.copy(contactsJwtUrl)
|
.copy(contactsJwtUrl)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.notify.copied(
|
this.notify.copied("contact link", TIMEOUTS.STANDARD);
|
||||||
"contact link",
|
|
||||||
TIMEOUTS.STANDARD,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,10 @@ import EntityIcon from "../components/EntityIcon.vue";
|
|||||||
import { NotificationIface } from "../constants/app";
|
import { NotificationIface } from "../constants/app";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
import { OfferSummaryRecord, OfferToPlanSummaryRecord } from "../interfaces/records";
|
import {
|
||||||
|
OfferSummaryRecord,
|
||||||
|
OfferToPlanSummaryRecord,
|
||||||
|
} from "../interfaces/records";
|
||||||
import {
|
import {
|
||||||
didInfo,
|
didInfo,
|
||||||
displayAmount,
|
displayAmount,
|
||||||
@@ -195,7 +198,7 @@ export default class NewActivityView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
|
|||||||
@@ -757,7 +757,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
*/
|
*/
|
||||||
async created() {
|
async created() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
@@ -806,10 +806,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
useClipboard()
|
useClipboard()
|
||||||
.copy(deepLink)
|
.copy(deepLink)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.notify.copied(
|
this.notify.copied("link to this project", TIMEOUTS.SHORT);
|
||||||
"link to this project",
|
|
||||||
TIMEOUTS.SHORT,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1376,10 +1373,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
this.axios,
|
this.axios,
|
||||||
);
|
);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.notify.success(
|
this.notify.success("Confirmation submitted.", TIMEOUTS.LONG);
|
||||||
"Confirmation submitted.",
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
this.recentlyCheckedAndUnconfirmableJwts = [
|
this.recentlyCheckedAndUnconfirmableJwts = [
|
||||||
...this.recentlyCheckedAndUnconfirmableJwts,
|
...this.recentlyCheckedAndUnconfirmableJwts,
|
||||||
give.jwtId,
|
give.jwtId,
|
||||||
@@ -1389,10 +1383,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
const message =
|
const message =
|
||||||
(result.error as string) ||
|
(result.error as string) ||
|
||||||
"There was a problem submitting the confirmation.";
|
"There was a problem submitting the confirmation.";
|
||||||
this.notify.error(
|
this.notify.error(message, TIMEOUTS.LONG);
|
||||||
message,
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export default class RecentOffersToUserView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export default class RecentOffersToUserView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
this.notify = createNotifyHelpers(this.$notify);
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = await this.$accountSettings();
|
const settings = await this.$accountSettings();
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
|
|||||||
@@ -57,12 +57,6 @@ import { Account } from "@/db/tables/accounts";
|
|||||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||||
|
|
||||||
// Constants for magic numbers
|
// Constants for magic numbers
|
||||||
const NOTIFICATION_TIMEOUTS = {
|
|
||||||
COPY_SUCCESS: 5000,
|
|
||||||
SHARE_CONTACTS: 10000,
|
|
||||||
ERROR: 5000,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const DELAYS = {
|
const DELAYS = {
|
||||||
SHARE_CONTACTS_DELAY: 3000,
|
SHARE_CONTACTS_DELAY: 3000,
|
||||||
} as const;
|
} as const;
|
||||||
@@ -153,10 +147,7 @@ export default class ShareMyContactInfoView extends Vue {
|
|||||||
* Show success notifications after copying
|
* Show success notifications after copying
|
||||||
*/
|
*/
|
||||||
private async showSuccessNotifications(): Promise<void> {
|
private async showSuccessNotifications(): Promise<void> {
|
||||||
this.notify.copied(
|
this.notify.copied("contact info", TIMEOUTS.LONG);
|
||||||
"contact info",
|
|
||||||
TIMEOUTS.LONG,
|
|
||||||
);
|
|
||||||
|
|
||||||
const numContacts = await this.$contactCount();
|
const numContacts = await this.$contactCount();
|
||||||
if (numContacts > 0) {
|
if (numContacts > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user