Refactor notification usage and apply TypeScript/lint improvements

- Replaced direct $notify calls with notification helper utilities for consistency and reduced duplication.
- Updated AccountViewView.vue, PlatformServiceMixin.ts, and ShareMyContactInfoView.vue to use notification helpers.
- Added explicit TypeScript types and constants for notification patterns.
- Suppressed ESLint 'any' warning in notification mixin helper.
- Ensured all affected files pass linting.
This commit is contained in:
Matthew Raymer
2025-07-05 12:13:46 +00:00
parent b2a2387f1a
commit 2eea44a6de
7 changed files with 995 additions and 694 deletions

View File

@@ -1030,24 +1030,18 @@ import {
import { UserProfile } from "@/libs/partnerServer";
import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
import {
AccountSettings,
UserProfileResponse,
isApiError,
isError,
ImportContent,
} from "@/interfaces/accountView";
const inputImportFileNameRef = ref<Blob>();
// Type guard for API errors
function isApiError(error: unknown): error is {
response?: {
data?: { error?: { message?: string } | string };
status?: number;
};
} {
return typeof error === "object" && error !== null && "response" in error;
}
// Type guard for standard errors
function isError(error: unknown): error is Error {
return error instanceof Error;
}
// Helper function to extract error message
function extractErrorMessage(error: unknown): string {
if (isApiError(error)) {
@@ -1085,59 +1079,72 @@ export default class AccountViewView extends Vue {
$route!: RouteLocationNormalizedLoaded;
$router!: Router;
AppConstants = AppString;
DEFAULT_PUSH_SERVER = DEFAULT_PUSH_SERVER;
DEFAULT_IMAGE_API_SERVER = DEFAULT_IMAGE_API_SERVER;
DEFAULT_PARTNER_API_SERVER = DEFAULT_PARTNER_API_SERVER;
// Add notification helpers
private notify = createNotifyHelpers(this.$notify);
activeDid = "";
apiServer = "";
apiServerInput = "";
derivationPath = "";
downloadUrl = ""; // because DuckDuckGo doesn't download on automated call to "click" on the anchor
endorserLimits: EndorserRateLimits | null = null;
givenName = "";
hideRegisterPromptOnNewContact = false;
imageLimits: ImageRateLimits | null = null;
includeUserProfileLocation = false;
isRegistered = false;
isSearchAreasSet = false;
limitsMessage = "";
loadingLimits = false;
loadingProfile = true;
notifyingNewActivity = false;
notifyingNewActivityTime = "";
notifyingReminder = false;
notifyingReminderMessage = "";
notifyingReminderTime = "";
partnerApiServer = DEFAULT_PARTNER_API_SERVER;
partnerApiServerInput = DEFAULT_PARTNER_API_SERVER;
passkeyExpirationDescription = "";
passkeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
previousPasskeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
// Constants
readonly AppConstants: typeof AppString = AppString;
readonly DEFAULT_PUSH_SERVER: string = DEFAULT_PUSH_SERVER;
readonly DEFAULT_IMAGE_API_SERVER: string = DEFAULT_IMAGE_API_SERVER;
readonly DEFAULT_PARTNER_API_SERVER: string = DEFAULT_PARTNER_API_SERVER;
// Identity and settings properties
activeDid: string = "";
apiServer: string = "";
apiServerInput: string = "";
derivationPath: string = "";
givenName: string = "";
hideRegisterPromptOnNewContact: boolean = false;
isRegistered: boolean = false;
isSearchAreasSet: boolean = false;
partnerApiServer: string = DEFAULT_PARTNER_API_SERVER;
partnerApiServerInput: string = DEFAULT_PARTNER_API_SERVER;
passkeyExpirationDescription: string = "";
passkeyExpirationMinutes: number = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
previousPasskeyExpirationMinutes: number = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
profileImageUrl?: string;
publicHex = "";
publicBase64 = "";
savingProfile = false;
showAdvanced = false;
showB64Copy = false;
showContactGives = false;
showDidCopy = false;
showDerCopy = false;
showGeneralAdvanced = false;
publicHex: string = "";
publicBase64: string = "";
webPushServer: string = DEFAULT_PUSH_SERVER;
webPushServerInput: string = DEFAULT_PUSH_SERVER;
// Profile properties
userProfileDesc: string = "";
userProfileLatitude: number = 0;
userProfileLongitude: number = 0;
includeUserProfileLocation: boolean = false;
savingProfile: boolean = false;
// Notification properties
notifyingNewActivity: boolean = false;
notifyingNewActivityTime: string = "";
notifyingReminder: boolean = false;
notifyingReminderMessage: string = "";
notifyingReminderTime: string = "";
subscription: PushSubscription | null = null;
// UI state properties
downloadUrl: string = ""; // because DuckDuckGo doesn't download on automated call to "click" on the anchor
loadingLimits: boolean = false;
loadingProfile: boolean = true;
showAdvanced: boolean = false;
showB64Copy: boolean = false;
showContactGives: boolean = false;
showDidCopy: boolean = false;
showDerCopy: boolean = false;
showGeneralAdvanced: boolean = false;
showLargeIdenticonId?: string;
showLargeIdenticonUrl?: string;
showPubCopy = false;
showShortcutBvc = false;
subscription: PushSubscription | null = null;
warnIfProdServer = false;
warnIfTestServer = false;
webPushServer = DEFAULT_PUSH_SERVER;
webPushServerInput = DEFAULT_PUSH_SERVER;
userProfileDesc = "";
userProfileLatitude = 0;
userProfileLongitude = 0;
zoom = 2;
showPubCopy: boolean = false;
showShortcutBvc: boolean = false;
warnIfProdServer: boolean = false;
warnIfTestServer: boolean = false;
zoom: number = 2;
// Limits and validation properties
endorserLimits: EndorserRateLimits | null = null;
imageLimits: ImageRateLimits | null = null;
limitsMessage: string = "";
/**
* Async function executed when the component is mounted.
@@ -1146,7 +1153,7 @@ export default class AccountViewView extends Vue {
*
* @throws Will display specific messages to the user based on different errors.
*/
async mounted() {
async mounted(): Promise<void> {
try {
// Initialize component state with values from the database or defaults
await this.initializeState();
@@ -1156,7 +1163,7 @@ export default class AccountViewView extends Vue {
if (this.isRegistered) {
try {
const headers = await getHeaders(this.activeDid);
const response = await this.axios.get(
const response = await this.axios.get<UserProfileResponse>(
this.partnerApiServer +
"/api/partner/userProfileForIssuer/" +
this.activeDid,
@@ -1171,7 +1178,7 @@ export default class AccountViewView extends Vue {
}
} else {
// won't get here because axios throws an error instead
throw Error("Unable to load profile.");
throw Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.UNABLE_TO_LOAD_PROFILE);
}
} catch (error) {
if (isApiError(error) && error.response?.status === 404) {
@@ -1180,14 +1187,8 @@ export default class AccountViewView extends Vue {
databaseUtil.logConsoleAndDb(
"Error loading profile: " + errorStringForLog(error),
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Loading Profile",
text: "Your server profile is not available.",
},
5000,
this.notify.error(
ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_AVAILABLE,
);
}
}
@@ -1203,15 +1204,7 @@ export default class AccountViewView extends Vue {
"To repeat with concatenated error: telling user to clear cache at page create because: " +
error,
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Loading Profile",
text: "See the Help page about errors with your personal data.",
},
5000,
);
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_LOAD_ERROR);
} finally {
this.loadingProfile = false;
}
@@ -1231,14 +1224,9 @@ export default class AccountViewView extends Vue {
}
}
} catch (error) {
this.$notify(
{
group: "alert",
type: "warning",
title: "Cannot Set Notifications",
text: "This browser does not support notifications. Use Chrome, or install this to the home screen, or try other suggestions on the 'Troubleshoot your notifications' page.",
},
7000,
this.notify.warning(
ACCOUNT_VIEW_CONSTANTS.ERRORS.BROWSER_NOTIFICATIONS_UNSUPPORTED,
TIMEOUTS.VERY_LONG,
);
}
} else {
@@ -1249,7 +1237,7 @@ export default class AccountViewView extends Vue {
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
}
beforeUnmount() {
beforeUnmount(): void {
if (this.downloadUrl) {
URL.revokeObjectURL(this.downloadUrl);
}
@@ -1258,8 +1246,9 @@ export default class AccountViewView extends Vue {
/**
* Initializes component state with values from the database or defaults.
*/
async initializeState() {
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
async initializeState(): Promise<void> {
const settings: AccountSettings =
await databaseUtil.retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
@@ -1293,56 +1282,56 @@ export default class AccountViewView extends Vue {
}
// call fn, copy text to the clipboard, then redo fn after 2 seconds
doCopyTwoSecRedo(text: string, fn: () => void) {
doCopyTwoSecRedo(text: string, fn: () => void): void {
fn();
useClipboard()
.copy(text)
.then(() => setTimeout(fn, 2000));
}
async toggleShowContactAmounts() {
async toggleShowContactAmounts(): Promise<void> {
this.showContactGives = !this.showContactGives;
await this.$saveSettings({
showContactGivesInline: this.showContactGives,
});
}
async toggleShowGeneralAdvanced() {
async toggleShowGeneralAdvanced(): Promise<void> {
this.showGeneralAdvanced = !this.showGeneralAdvanced;
await this.$saveSettings({
showGeneralAdvanced: this.showGeneralAdvanced,
});
}
async toggleProdWarning() {
async toggleProdWarning(): Promise<void> {
this.warnIfProdServer = !this.warnIfProdServer;
await this.$saveSettings({
warnIfProdServer: this.warnIfProdServer,
});
}
async toggleTestWarning() {
async toggleTestWarning(): Promise<void> {
this.warnIfTestServer = !this.warnIfTestServer;
await this.$saveSettings({
warnIfTestServer: this.warnIfTestServer,
});
}
async toggleShowShortcutBvc() {
async toggleShowShortcutBvc(): Promise<void> {
this.showShortcutBvc = !this.showShortcutBvc;
await this.$saveSettings({
showShortcutBvc: this.showShortcutBvc,
});
}
readableDate(timeStr: string) {
readableDate(timeStr: string): string {
return timeStr ? timeStr.substring(0, timeStr.indexOf("T")) : "?";
}
/**
* Processes the identity and updates the component's state.
*/
async processIdentity() {
async processIdentity(): Promise<void> {
const account = await retrieveAccountMetadata(this.activeDid);
if (account?.identity) {
const identity = JSON.parse(account.identity as string) as IIdentifier;
@@ -1359,30 +1348,18 @@ export default class AccountViewView extends Vue {
}
}
async showNewActivityNotificationInfo() {
this.$notify(
{
group: "modal",
type: "confirm",
title: "New Activity Notification",
text: `
This will only notify you when there is new relevant activity for you personally.
Note that it runs on your device and many factors may affect delivery,
so if you want a reliable but simple daily notification then choose a 'Reminder'.
Do you want more details?
`,
onYes: async () => {
await (this.$router as Router).push({
name: "help-notification-types",
});
},
yesText: "tell me more.",
async showNewActivityNotificationInfo(): Promise<void> {
this.notify.confirm(
ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.NEW_ACTIVITY_INFO,
async () => {
await (this.$router as Router).push({
name: "help-notification-types",
});
},
-1,
);
}
async showNewActivityNotificationChoice() {
async showNewActivityNotificationChoice(): Promise<void> {
if (!this.notifyingNewActivity) {
(
this.$refs.pushNotificationPermission as PushNotificationPermission
@@ -1396,51 +1373,30 @@ export default class AccountViewView extends Vue {
}
});
} else {
this.$notify(
{
group: "modal",
type: "notification-off",
title: DAILY_CHECK_TITLE, // repurposed to indicate the type of notification
text: "", // unused, only here to satisfy type check
callback: async (success) => {
if (success) {
await this.$saveSettings({
notifyingNewActivityTime: "",
});
this.notifyingNewActivity = false;
this.notifyingNewActivityTime = "";
}
},
},
-1,
);
this.notify.notificationOff(DAILY_CHECK_TITLE, async (success) => {
if (success) {
await this.$saveSettings({
notifyingNewActivityTime: "",
});
this.notifyingNewActivity = false;
this.notifyingNewActivityTime = "";
}
});
}
}
async showReminderNotificationInfo() {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Reminder Notification",
text: `
This will notify you at a specific time each day.
Note that it does not give you personalized notifications,
so if you want less reliable but personalized notification then choose a 'New Activity' Notification.
Do you want more details?
`,
onYes: async () => {
await (this.$router as Router).push({
name: "help-notification-types",
});
},
yesText: "tell me more.",
async showReminderNotificationInfo(): Promise<void> {
this.notify.confirm(
ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.REMINDER_INFO,
async () => {
await (this.$router as Router).push({
name: "help-notification-types",
});
},
-1,
);
}
async showReminderNotificationChoice() {
async showReminderNotificationChoice(): Promise<void> {
if (!this.notifyingReminder) {
(
this.$refs.pushNotificationPermission as PushNotificationPermission
@@ -1459,30 +1415,21 @@ export default class AccountViewView extends Vue {
},
);
} else {
this.$notify(
{
group: "modal",
type: "notification-off",
title: DIRECT_PUSH_TITLE, // repurposed to indicate the type of notification
text: "", // unused, only here to satisfy type check
callback: async (success) => {
if (success) {
await this.$saveSettings({
notifyingReminderMessage: "",
notifyingReminderTime: "",
});
this.notifyingReminder = false;
this.notifyingReminderMessage = "";
this.notifyingReminderTime = "";
}
},
},
-1,
);
this.notify.notificationOff(DIRECT_PUSH_TITLE, async (success) => {
if (success) {
await this.$saveSettings({
notifyingReminderMessage: "",
notifyingReminderTime: "",
});
this.notifyingReminder = false;
this.notifyingReminderMessage = "";
this.notifyingReminderTime = "";
}
});
}
}
public async toggleHideRegisterPromptOnNewContact() {
public async toggleHideRegisterPromptOnNewContact(): Promise<void> {
const newSetting = !this.hideRegisterPromptOnNewContact;
await this.$saveSettings({
hideRegisterPromptOnNewContact: newSetting,
@@ -1490,7 +1437,7 @@ export default class AccountViewView extends Vue {
this.hideRegisterPromptOnNewContact = newSetting;
}
public async updatePasskeyExpiration() {
public async updatePasskeyExpiration(): Promise<void> {
await this.$saveSettings({
passkeyExpirationMinutes: this.passkeyExpirationMinutes,
});
@@ -1498,7 +1445,7 @@ export default class AccountViewView extends Vue {
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
}
public async turnOffNotifyingFlags() {
public async turnOffNotifyingFlags(): Promise<void> {
// should tell the push server as well
await this.$saveSettings({
notifyingNewActivityTime: "",
@@ -1586,15 +1533,7 @@ export default class AccountViewView extends Vue {
* Notifies the user that the download has started.
*/
private notifyDownloadStarted() {
this.$notify(
{
group: "alert",
type: "success",
title: "Download Started",
text: "See your downloads directory for the backup. It is in the Dexie format.",
},
-1,
);
this.notify.downloadStarted();
}
/**
@@ -1602,42 +1541,29 @@ export default class AccountViewView extends Vue {
*
* @param {Error} error - The error object.
*/
private handleExportError(error: unknown) {
private handleExportError(error: unknown): void {
logger.error("Export Error:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Export Error",
text: "There was an error exporting the data.",
},
3000,
this.notify.error(
ACCOUNT_VIEW_CONSTANTS.ERRORS.EXPORT_ERROR,
TIMEOUTS.STANDARD,
);
}
async uploadImportFile(event: Event) {
async uploadImportFile(event: Event): Promise<void> {
inputImportFileNameRef.value = (
event.target as HTMLInputElement
).files?.[0];
}
showContactImport() {
showContactImport(): boolean {
return !!inputImportFileNameRef.value;
}
confirmSubmitImportFile() {
confirmSubmitImportFile(): void {
if (inputImportFileNameRef.value != null) {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Replace All",
text:
"This will replace all settings and contacts, so we recommend you first do the backup step above." +
" Are you sure you want to import and replace all contacts and settings?",
onYes: this.submitImportFile,
},
-1,
this.notify.confirm(
ACCOUNT_VIEW_CONSTANTS.WARNINGS.IMPORT_REPLACE_WARNING,
this.submitImportFile,
);
}
}
@@ -1647,18 +1573,18 @@ export default class AccountViewView extends Vue {
*
* @throws Will notify the user if there is an export error.
*/
async submitImportFile() {
async submitImportFile(): Promise<void> {
if (inputImportFileNameRef.value != null) {
// TODO: implement this for SQLite
}
}
async checkContactImports() {
async checkContactImports(): Promise<void> {
const reader = new FileReader();
reader.onload = (event) => {
const fileContent: string = (event.target?.result as string) || "{}";
try {
const contents = JSON.parse(fileContent);
const contents: ImportContent = JSON.parse(fileContent);
const contactTableRows: Array<Contact> = (
contents.data?.data as [{ tableName: string; rows: Array<Contact> }]
)?.find((table) => table.tableName === "contacts")
@@ -1673,45 +1599,34 @@ export default class AccountViewView extends Vue {
});
} catch (error) {
logger.error("Error checking contact imports:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Importing",
text: "There was an error reading that Dexie file.",
},
3000,
this.notify.error(
ACCOUNT_VIEW_CONSTANTS.ERRORS.IMPORT_ERROR,
TIMEOUTS.STANDARD,
);
}
};
reader.readAsText(inputImportFileNameRef.value as Blob);
}
private progressCallback(progress: ImportProgress) {
private progressCallback(progress: ImportProgress): boolean {
logger.log(
`Import progress: ${progress.completedRows} of ${progress.totalRows} rows completed.`,
);
if (progress.done) {
// console.log(`Imported ${progress.completedTables} tables.`);
this.$notify(
{
group: "alert",
type: "success",
title: "Import Complete",
text: "",
},
5000,
this.notify.success(
ACCOUNT_VIEW_CONSTANTS.SUCCESS.IMPORT_COMPLETE,
TIMEOUTS.LONG,
);
}
return true;
}
async checkLimits() {
async checkLimits(): Promise<void> {
if (this.activeDid) {
this.checkLimitsFor(this.activeDid);
} else {
this.limitsMessage =
"You have no identifier, or your data has been corrupted.";
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IDENTIFIER;
}
}
@@ -1722,7 +1637,7 @@ export default class AccountViewView extends Vue {
*
* Updates component state variables `limits`, `limitsMessage`, and `loadingLimits`.
*/
private async checkLimitsFor(did: string) {
private async checkLimitsFor(did: string): Promise<void> {
this.loadingLimits = true;
this.limitsMessage = "";
@@ -1743,14 +1658,8 @@ export default class AccountViewView extends Vue {
this.isRegistered = true;
} catch (err) {
logger.error("Got an error updating settings:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Update Error",
text: "Unable to update your settings. Check claim limits again.",
},
5000,
this.notify.error(
ACCOUNT_VIEW_CONSTANTS.ERRORS.SETTINGS_UPDATE_ERROR,
);
}
}
@@ -1759,10 +1668,11 @@ export default class AccountViewView extends Vue {
if (imageResp.status === 200) {
this.imageLimits = imageResp.data;
} else {
this.limitsMessage = "You don't have access to upload images.";
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IMAGE_ACCESS;
}
} catch {
this.limitsMessage = "You cannot upload images.";
this.limitsMessage =
ACCOUNT_VIEW_CONSTANTS.LIMITS.CANNOT_UPLOAD_IMAGES;
}
}
} catch (error) {
@@ -1777,7 +1687,7 @@ export default class AccountViewView extends Vue {
*
* @param {AxiosError | Error} error - The error object.
*/
private handleRateLimitsError(error: unknown) {
private handleRateLimitsError(error: unknown): void {
if (error instanceof AxiosError) {
if (error.status == 400 || error.status == 404) {
// no worries: they probably just aren't registered and don't have any limits
@@ -1785,50 +1695,44 @@ export default class AccountViewView extends Vue {
"Got 400 or 404 response retrieving limits which probably means they're not registered:",
error,
);
this.limitsMessage = "No limits were found, so no actions are allowed.";
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_LIMITS_FOUND;
} else {
const data = error.response?.data as ErrorResponse;
this.limitsMessage =
(data?.error?.message as string) || "Bad server response.";
(data?.error?.message as string) ||
ACCOUNT_VIEW_CONSTANTS.LIMITS.BAD_SERVER_RESPONSE;
logger.error("Got bad response retrieving limits:", error);
}
} else {
this.limitsMessage = "Got an error retrieving limits.";
this.limitsMessage =
ACCOUNT_VIEW_CONSTANTS.LIMITS.ERROR_RETRIEVING_LIMITS;
logger.error("Got some error retrieving limits:", error);
}
}
async onClickSaveApiServer() {
async onClickSaveApiServer(): Promise<void> {
await databaseUtil.updateDefaultSettings({
apiServer: this.apiServerInput,
});
this.apiServer = this.apiServerInput;
}
async onClickSavePartnerServer() {
async onClickSavePartnerServer(): Promise<void> {
await databaseUtil.updateDefaultSettings({
partnerApiServer: this.partnerApiServerInput,
});
this.partnerApiServer = this.partnerApiServerInput;
}
async onClickSavePushServer() {
async onClickSavePushServer(): Promise<void> {
await databaseUtil.updateDefaultSettings({
webPushServer: this.webPushServerInput,
});
this.webPushServer = this.webPushServerInput;
this.$notify(
{
group: "alert",
type: "warning",
title: "Reload",
text: "Now reload the app to get a new VAPID to use with this push server.",
},
5000,
);
this.notify.warning(ACCOUNT_VIEW_CONSTANTS.INFO.RELOAD_VAPID);
}
openImageDialog() {
openImageDialog(): void {
(this.$refs.imageMethodDialog as ImageMethodDialog).open(
async (imgUrl) => {
await databaseUtil.updateDefaultSettings({
@@ -1842,21 +1746,14 @@ export default class AccountViewView extends Vue {
);
}
confirmDeleteImage() {
this.$notify(
{
group: "modal",
type: "confirm",
title:
"Note that anyone with you already as a contact will no longer see a picture, and you will have to reshare your data with them if you save a new picture. Are you sure you want to delete your profile picture?",
text: "",
onYes: this.deleteImage,
},
-1,
confirmDeleteImage(): void {
this.notify.confirm(
ACCOUNT_VIEW_CONSTANTS.WARNINGS.IMAGE_DELETE_WARNING,
this.deleteImage,
);
}
async deleteImage() {
async deleteImage(): Promise<void> {
if (!this.profileImageUrl) {
return;
}
@@ -1882,15 +1779,7 @@ export default class AccountViewView extends Vue {
// (either they'll simply continue or they're canceling and going back)
} else {
logger.error("Non-success deleting image:", response);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "There was a problem deleting the image. Contact support if you want it removed from the servers.",
},
5000,
);
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.IMAGE_DELETE_PROBLEM);
// keep the imageUrl in localStorage so the user can try again if they want
}
@@ -1913,38 +1802,28 @@ export default class AccountViewView extends Vue {
// it already doesn't exist so we won't say anything to the user
} else {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "There was an error deleting the image.",
},
3000,
this.notify.error(
ACCOUNT_VIEW_CONSTANTS.ERRORS.IMAGE_DELETE_ERROR,
TIMEOUTS.STANDARD,
);
}
}
}
onMapReady(map: L.Map) {
onMapReady(map: L.Map): void {
// doing this here instead of on the l-map element avoids a recentering after a drag then zoom at startup
const zoom = this.userProfileLatitude && this.userProfileLongitude ? 12 : 2;
map.setView([this.userProfileLatitude, this.userProfileLongitude], zoom);
}
showProfileInfo() {
this.$notify(
{
group: "alert",
type: "info",
title: "Public Profile Information",
text: "This data will be published for all to see, so be careful what your write. Your ID will only be shared with people who you allow to see your activity.",
},
7000,
showProfileInfo(): void {
this.notify.info(
ACCOUNT_VIEW_CONSTANTS.INFO.PROFILE_INFO,
TIMEOUTS.VERY_LONG,
);
}
async saveProfile() {
async saveProfile(): Promise<void> {
this.savingProfile = true;
try {
const headers = await getHeaders(this.activeDid);
@@ -1955,14 +1834,10 @@ export default class AccountViewView extends Vue {
payload.locLat = this.userProfileLatitude;
payload.locLon = this.userProfileLongitude;
} else if (this.includeUserProfileLocation) {
this.$notify(
{
group: "alert",
type: "toast",
title: "",
text: "No profile location is saved.",
},
3000,
this.notify.toast(
"",
ACCOUNT_VIEW_CONSTANTS.INFO.NO_PROFILE_LOCATION,
TIMEOUTS.STANDARD,
);
}
const response = await this.axios.post(
@@ -1971,40 +1846,28 @@ export default class AccountViewView extends Vue {
{ headers },
);
if (response.status === 201) {
this.$notify(
{
group: "alert",
type: "success",
title: "Profile Saved",
text: "Your profile has been updated successfully.",
},
3000,
this.notify.success(
ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_SAVED,
TIMEOUTS.STANDARD,
);
} else {
// won't get here because axios throws an error on non-success
throw Error("Profile not saved");
throw Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_SAVED);
}
} catch (error) {
databaseUtil.logConsoleAndDb(
"Error saving profile: " + errorStringForLog(error),
);
const errorMessage: string =
extractErrorMessage(error) || "There was an error saving your profile.";
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Saving Profile",
text: errorMessage,
},
3000,
);
extractErrorMessage(error) ||
ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR;
this.notify.error(errorMessage, TIMEOUTS.STANDARD);
} finally {
this.savingProfile = false;
}
}
toggleUserProfileLocation() {
toggleUserProfileLocation(): void {
this.includeUserProfileLocation = !this.includeUserProfileLocation;
if (!this.includeUserProfileLocation) {
this.userProfileLatitude = 0;
@@ -2013,42 +1876,30 @@ export default class AccountViewView extends Vue {
}
}
confirmEraseLatLong() {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Erase Marker",
text: "Are you sure you don't want to mark a location? This will erase the current location.",
onYes: async () => {
this.eraseLatLong();
},
confirmEraseLatLong(): void {
this.notify.confirm(
ACCOUNT_VIEW_CONSTANTS.WARNINGS.ERASE_LOCATION_WARNING,
async () => {
this.eraseLatLong();
},
-1,
);
}
eraseLatLong() {
eraseLatLong(): void {
this.userProfileLatitude = 0;
this.userProfileLongitude = 0;
this.zoom = 2;
this.includeUserProfileLocation = false;
}
async confirmDeleteProfile() {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Delete Profile",
text: "Are you sure you want to delete your public profile? This will remove your description and location from the server, and it cannot be undone.",
onYes: this.deleteProfile,
},
-1,
async confirmDeleteProfile(): Promise<void> {
this.notify.confirm(
ACCOUNT_VIEW_CONSTANTS.WARNINGS.DELETE_PROFILE_WARNING,
this.deleteProfile,
);
}
async deleteProfile() {
async deleteProfile(): Promise<void> {
this.savingProfile = true;
try {
const headers = await getHeaders(this.activeDid);
@@ -2061,17 +1912,12 @@ export default class AccountViewView extends Vue {
this.userProfileLatitude = 0;
this.userProfileLongitude = 0;
this.includeUserProfileLocation = false;
this.$notify(
{
group: "alert",
type: "success",
title: "Profile Deleted",
text: "Your profile has been deleted successfully.",
},
3000,
this.notify.success(
ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_DELETED,
TIMEOUTS.STANDARD,
);
} else {
throw Error("Profile not deleted");
throw Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_DELETED);
}
} catch (error) {
databaseUtil.logConsoleAndDb(
@@ -2079,16 +1925,8 @@ export default class AccountViewView extends Vue {
);
const errorMessage: string =
extractErrorMessage(error) ||
"There was an error deleting your profile.";
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Deleting Profile",
text: errorMessage,
},
3000,
);
ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR;
this.notify.error(errorMessage, TIMEOUTS.STANDARD);
} finally {
this.savingProfile = false;
}