forked from trent_larson/crowd-funder-for-time-pwa
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:
@@ -1,5 +1,6 @@
|
||||
import { inject } from 'vue';
|
||||
import { NotificationIface } from '../constants/app';
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { inject } from "vue";
|
||||
import { NotificationIface } from "../constants/app";
|
||||
|
||||
/**
|
||||
* Vue 3 composable for notifications
|
||||
@@ -7,134 +8,90 @@ import { NotificationIface } from '../constants/app';
|
||||
*/
|
||||
|
||||
export const NOTIFICATION_TIMEOUTS = {
|
||||
BRIEF: 1000, // Very brief toasts ("Sent..." messages)
|
||||
SHORT: 2000, // Short notifications (clipboard copies, quick confirmations)
|
||||
STANDARD: 3000, // Standard notifications (success messages, general info)
|
||||
LONG: 5000, // Longer notifications (errors, warnings, important info)
|
||||
VERY_LONG: 7000, // Very long notifications (complex operations)
|
||||
MODAL: -1, // Modal confirmations (no auto-dismiss)
|
||||
BRIEF: 1000, // Very brief toasts ("Sent..." messages)
|
||||
SHORT: 2000, // Short notifications (clipboard copies, quick confirmations)
|
||||
STANDARD: 3000, // Standard notifications (success messages, general info)
|
||||
LONG: 5000, // Longer notifications (errors, warnings, important info)
|
||||
VERY_LONG: 7000, // Very long notifications (complex operations)
|
||||
MODAL: -1, // Modal confirmations (no auto-dismiss)
|
||||
} as const;
|
||||
|
||||
export function useNotifications() {
|
||||
// Inject the notify function from the app
|
||||
const notify = inject<(notification: NotificationIface, timeout?: number) => void>('$notify');
|
||||
const notify =
|
||||
inject<(notification: NotificationIface, timeout?: number) => void>(
|
||||
"notify",
|
||||
);
|
||||
|
||||
if (!notify) {
|
||||
throw new Error('useNotifications must be used within a component that has $notify available');
|
||||
throw new Error(
|
||||
"useNotifications must be used within a component that has $notify available",
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function success(_notification: NotificationIface, _timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function error(_notification: NotificationIface, _timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function warning(_notification: NotificationIface, _timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function info(_notification: NotificationIface, _timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function toast(_title: string, _text?: string, _timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function copied(_item: string, _timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function sent(_timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function confirm(
|
||||
_text: string,
|
||||
_onYes: () => Promise<void>,
|
||||
_timeout?: number,
|
||||
) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function confirmationSubmitted(_timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function genericError(_timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function genericSuccess(_timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function alreadyConfirmed(_timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function cannotConfirmIssuer(_timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function cannotConfirmHidden(_timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function notRegistered(_timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function notAGive(_timeout?: number) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function notificationOff(
|
||||
_title: string,
|
||||
_callback: (success: boolean) => Promise<void>,
|
||||
_timeout?: number,
|
||||
) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function downloadStarted(_format: string = "Dexie", _timeout?: number) {}
|
||||
|
||||
return {
|
||||
// Direct access to the original notify function
|
||||
notify,
|
||||
|
||||
// Success notifications
|
||||
success: (text: string, timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text,
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Error notifications
|
||||
error: (text: string, timeout = NOTIFICATION_TIMEOUTS.LONG) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text,
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Warning notifications
|
||||
warning: (text: string, timeout = NOTIFICATION_TIMEOUTS.LONG) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: "Warning",
|
||||
text,
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Info notifications
|
||||
info: (text: string, timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Info",
|
||||
text,
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Toast notifications (brief)
|
||||
toast: (title: string, text?: string, timeout = NOTIFICATION_TIMEOUTS.BRIEF) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title,
|
||||
text,
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Clipboard copy notifications
|
||||
copied: (item: string, timeout = NOTIFICATION_TIMEOUTS.SHORT) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: "Copied",
|
||||
text: `${item} was copied to the clipboard.`,
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Sent brief notification
|
||||
sent: (timeout = NOTIFICATION_TIMEOUTS.BRIEF) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: "Sent...",
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Confirmation modal
|
||||
confirm: (text: string, onYes: () => Promise<void>, timeout = NOTIFICATION_TIMEOUTS.MODAL) => {
|
||||
notify({
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Confirm",
|
||||
text,
|
||||
onYes,
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Standard confirmation messages
|
||||
confirmationSubmitted: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text: "Confirmation submitted.",
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Common error patterns
|
||||
genericError: (timeout = NOTIFICATION_TIMEOUTS.LONG) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "Something went wrong.",
|
||||
}, timeout);
|
||||
},
|
||||
|
||||
// Common success patterns
|
||||
genericSuccess: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text: "Operation completed successfully.",
|
||||
}, timeout);
|
||||
},
|
||||
success,
|
||||
error,
|
||||
warning,
|
||||
info,
|
||||
toast,
|
||||
copied,
|
||||
sent,
|
||||
confirm,
|
||||
confirmationSubmitted,
|
||||
genericError,
|
||||
genericSuccess,
|
||||
alreadyConfirmed,
|
||||
cannotConfirmIssuer,
|
||||
cannotConfirmHidden,
|
||||
notRegistered,
|
||||
notAGive,
|
||||
notificationOff,
|
||||
downloadStarted,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
128
src/constants/accountView.ts
Normal file
128
src/constants/accountView.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Constants for AccountViewView component
|
||||
* Centralizes magic strings and provides type safety
|
||||
*/
|
||||
|
||||
export const ACCOUNT_VIEW_CONSTANTS = {
|
||||
// Error messages
|
||||
ERRORS: {
|
||||
PROFILE_NOT_AVAILABLE: "Your server profile is not available.",
|
||||
PROFILE_LOAD_ERROR:
|
||||
"See the Help page about errors with your personal data.",
|
||||
BROWSER_NOTIFICATIONS_UNSUPPORTED:
|
||||
"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.",
|
||||
IMAGE_DELETE_PROBLEM:
|
||||
"There was a problem deleting the image. Contact support if you want it removed from the servers.",
|
||||
IMAGE_DELETE_ERROR: "There was an error deleting the image.",
|
||||
SETTINGS_UPDATE_ERROR:
|
||||
"Unable to update your settings. Check claim limits again.",
|
||||
IMPORT_ERROR: "There was an error reading that Dexie file.",
|
||||
EXPORT_ERROR: "There was an error exporting the data.",
|
||||
PROFILE_SAVE_ERROR: "There was an error saving your profile.",
|
||||
PROFILE_DELETE_ERROR: "There was an error deleting your profile.",
|
||||
PROFILE_NOT_SAVED: "Profile not saved",
|
||||
PROFILE_NOT_DELETED: "Profile not deleted",
|
||||
UNABLE_TO_LOAD_PROFILE: "Unable to load profile.",
|
||||
},
|
||||
|
||||
// Success messages
|
||||
SUCCESS: {
|
||||
PROFILE_SAVED: "Your profile has been updated successfully.",
|
||||
PROFILE_DELETED: "Your profile has been deleted successfully.",
|
||||
IMPORT_COMPLETE: "Import Complete",
|
||||
PROFILE_DELETED_SILENT: "Your profile has been deleted successfully.",
|
||||
},
|
||||
|
||||
// Info messages
|
||||
INFO: {
|
||||
PROFILE_INFO:
|
||||
"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.",
|
||||
NO_PROFILE_LOCATION: "No profile location is saved.",
|
||||
RELOAD_VAPID:
|
||||
"Now reload the app to get a new VAPID to use with this push server.",
|
||||
},
|
||||
|
||||
// Warning messages
|
||||
WARNINGS: {
|
||||
IMAGE_DELETE_WARNING:
|
||||
"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?",
|
||||
ERASE_LOCATION_WARNING:
|
||||
"Are you sure you don't want to mark a location? This will erase the current location.",
|
||||
DELETE_PROFILE_WARNING:
|
||||
"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.",
|
||||
IMPORT_REPLACE_WARNING:
|
||||
"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?",
|
||||
},
|
||||
|
||||
// Notification messages
|
||||
NOTIFICATIONS: {
|
||||
NEW_ACTIVITY_INFO: `
|
||||
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?
|
||||
`,
|
||||
REMINDER_INFO: `
|
||||
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?
|
||||
`,
|
||||
},
|
||||
|
||||
// UI text
|
||||
UI: {
|
||||
COPIED: "Copied",
|
||||
SENT: "Sent...",
|
||||
RECORDING_GIVE: "Recording the give...",
|
||||
RECORDING_OFFER: "Recording the offer...",
|
||||
},
|
||||
|
||||
// Limits messages
|
||||
LIMITS: {
|
||||
NO_IDENTIFIER: "You have no identifier, or your data has been corrupted.",
|
||||
NO_LIMITS_FOUND: "No limits were found, so no actions are allowed.",
|
||||
NO_IMAGE_ACCESS: "You don't have access to upload images.",
|
||||
CANNOT_UPLOAD_IMAGES: "You cannot upload images.",
|
||||
BAD_SERVER_RESPONSE: "Bad server response.",
|
||||
ERROR_RETRIEVING_LIMITS: "Got an error retrieving limits.",
|
||||
},
|
||||
|
||||
// Project assignment errors
|
||||
PROJECT_ERRORS: {
|
||||
MISSING_PROJECT:
|
||||
"To assign to a project, you must open this page through a project.",
|
||||
CONFLICT_RECIPIENT:
|
||||
"You cannot assign both to a project and to a recipient.",
|
||||
MISSING_RECIPIENT:
|
||||
"To assign to a recipient, you must open this page from a contact.",
|
||||
CONFLICT_PROJECT: "You cannot assign both to a recipient and to a project.",
|
||||
},
|
||||
|
||||
// Giver/Recipient errors
|
||||
GIVER_RECIPIENT_ERRORS: {
|
||||
MISSING_GIVER: "To assign a giver, you must open this page from a contact.",
|
||||
CONFLICT_PROJECT_GIVER: "You cannot assign both a giver and a project.",
|
||||
MISSING_RECIPIENT_GIFT:
|
||||
"To assign to a recipient, you must open this page from a contact.",
|
||||
CONFLICT_PROJECT_RECIPIENT:
|
||||
"You cannot assign both to a recipient and to a project.",
|
||||
MISSING_PROVIDER_PROJECT:
|
||||
"To select a project as a provider, you must open this page through a project.",
|
||||
CONFLICT_GIVING_PROJECT:
|
||||
"You cannot select both a giving project and person.",
|
||||
MISSING_FULFILLS_PROJECT:
|
||||
"To assign to a project, you must open this page through a project.",
|
||||
CONFLICT_FULFILLS_PROJECT:
|
||||
"You cannot assign both to a project and to a recipient.",
|
||||
},
|
||||
} as const;
|
||||
|
||||
// Type for accessing constants
|
||||
export type AccountViewConstants = typeof ACCOUNT_VIEW_CONSTANTS;
|
||||
|
||||
// Helper type for error messages
|
||||
export type ErrorMessageKey = keyof typeof ACCOUNT_VIEW_CONSTANTS.ERRORS;
|
||||
export type SuccessMessageKey = keyof typeof ACCOUNT_VIEW_CONSTANTS.SUCCESS;
|
||||
export type InfoMessageKey = keyof typeof ACCOUNT_VIEW_CONSTANTS.INFO;
|
||||
export type WarningMessageKey = keyof typeof ACCOUNT_VIEW_CONSTANTS.WARNINGS;
|
||||
232
src/interfaces/accountView.ts
Normal file
232
src/interfaces/accountView.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* TypeScript interfaces for AccountViewView component
|
||||
* Provides type safety for settings, profile data, and component state
|
||||
*/
|
||||
|
||||
import { EndorserRateLimits, ImageRateLimits } from "./index";
|
||||
import { LeafletMouseEvent } from "leaflet";
|
||||
|
||||
/**
|
||||
* BoundingBox type describes the geographical bounding box coordinates.
|
||||
*/
|
||||
export type BoundingBox = {
|
||||
eastLong: number; // Eastern longitude
|
||||
maxLat: number; // Maximum (Northernmost) latitude
|
||||
minLat: number; // Minimum (Southernmost) latitude
|
||||
westLong: number; // Western longitude
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for account settings retrieved from database
|
||||
*/
|
||||
export interface AccountSettings {
|
||||
activeDid?: string;
|
||||
apiServer?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
hideRegisterPromptOnNewContact?: boolean;
|
||||
isRegistered?: boolean;
|
||||
searchBoxes?: Array<{
|
||||
name: string;
|
||||
bbox: BoundingBox;
|
||||
}>;
|
||||
notifyingNewActivityTime?: string;
|
||||
notifyingReminderMessage?: string;
|
||||
notifyingReminderTime?: string;
|
||||
partnerApiServer?: string;
|
||||
profileImageUrl?: string;
|
||||
showContactGivesInline?: boolean;
|
||||
passkeyExpirationMinutes?: number;
|
||||
showGeneralAdvanced?: boolean;
|
||||
showShortcutBvc?: boolean;
|
||||
warnIfProdServer?: boolean;
|
||||
warnIfTestServer?: boolean;
|
||||
webPushServer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for user profile data from API
|
||||
*/
|
||||
export interface UserProfileData {
|
||||
description?: string;
|
||||
locLat?: number;
|
||||
locLon?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for API response containing user profile
|
||||
*/
|
||||
export interface UserProfileResponse {
|
||||
data: UserProfileData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for component state related to profile management
|
||||
*/
|
||||
export interface ProfileState {
|
||||
userProfileDesc: string;
|
||||
userProfileLatitude: number;
|
||||
userProfileLongitude: number;
|
||||
includeUserProfileLocation: boolean;
|
||||
savingProfile: boolean;
|
||||
profileImageUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for component state related to notifications
|
||||
*/
|
||||
export interface NotificationState {
|
||||
notifyingNewActivity: boolean;
|
||||
notifyingNewActivityTime: string;
|
||||
notifyingReminder: boolean;
|
||||
notifyingReminderMessage: string;
|
||||
notifyingReminderTime: string;
|
||||
subscription: PushSubscription | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for component state related to settings
|
||||
*/
|
||||
export interface SettingsState {
|
||||
activeDid: string;
|
||||
apiServer: string;
|
||||
apiServerInput: string;
|
||||
partnerApiServer: string;
|
||||
partnerApiServerInput: string;
|
||||
webPushServer: string;
|
||||
webPushServerInput: string;
|
||||
passkeyExpirationMinutes: number;
|
||||
previousPasskeyExpirationMinutes: number;
|
||||
passkeyExpirationDescription: string;
|
||||
hideRegisterPromptOnNewContact: boolean;
|
||||
isRegistered: boolean;
|
||||
isSearchAreasSet: boolean;
|
||||
showContactGives: boolean;
|
||||
showGeneralAdvanced: boolean;
|
||||
showShortcutBvc: boolean;
|
||||
warnIfProdServer: boolean;
|
||||
warnIfTestServer: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for component state related to UI display
|
||||
*/
|
||||
export interface UIState {
|
||||
loadingProfile: boolean;
|
||||
loadingLimits: boolean;
|
||||
showAdvanced: boolean;
|
||||
showB64Copy: boolean;
|
||||
showDidCopy: boolean;
|
||||
showDerCopy: boolean;
|
||||
showPubCopy: boolean;
|
||||
showLargeIdenticonId?: string;
|
||||
showLargeIdenticonUrl?: string;
|
||||
downloadUrl: string;
|
||||
zoom: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for component state related to limits and validation
|
||||
*/
|
||||
export interface LimitsState {
|
||||
endorserLimits: EndorserRateLimits | null;
|
||||
imageLimits: ImageRateLimits | null;
|
||||
limitsMessage: string;
|
||||
publicHex: string;
|
||||
publicBase64: string;
|
||||
derivationPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for component state related to identity
|
||||
*/
|
||||
export interface IdentityState {
|
||||
givenName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete interface for AccountViewView component state
|
||||
*/
|
||||
export interface AccountViewState
|
||||
extends ProfileState,
|
||||
NotificationState,
|
||||
SettingsState,
|
||||
UIState,
|
||||
LimitsState,
|
||||
IdentityState {}
|
||||
|
||||
/**
|
||||
* Interface for clipboard copy operations
|
||||
*/
|
||||
export interface ClipboardOperation {
|
||||
text: string;
|
||||
callback: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for notification permission callback
|
||||
*/
|
||||
export interface NotificationPermissionCallback {
|
||||
success: boolean;
|
||||
timeText: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for import/export operations
|
||||
*/
|
||||
export interface ImportExportState {
|
||||
inputImportFileNameRef?: Blob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for API error responses
|
||||
*/
|
||||
export interface ApiErrorResponse {
|
||||
response?: {
|
||||
data?: {
|
||||
error?: { message?: string } | string;
|
||||
};
|
||||
status?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for API errors
|
||||
*/
|
||||
export function isApiError(error: unknown): error is ApiErrorResponse {
|
||||
return typeof error === "object" && error !== null && "response" in error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for standard errors
|
||||
*/
|
||||
export function isError(error: unknown): error is Error {
|
||||
return error instanceof Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for file import content structure
|
||||
*/
|
||||
export interface ImportContent {
|
||||
data?: {
|
||||
data?: Array<{
|
||||
tableName: string;
|
||||
rows: Array<unknown>;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for map ready callback
|
||||
*/
|
||||
export interface MapReadyCallback {
|
||||
(map: L.Map): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for mouse event handlers
|
||||
*/
|
||||
export interface MouseEventHandler {
|
||||
(event: LeafletMouseEvent): void;
|
||||
}
|
||||
@@ -27,12 +27,12 @@ export interface NotificationHelper {
|
||||
* Standard notification timeouts
|
||||
*/
|
||||
export const NOTIFICATION_TIMEOUTS = {
|
||||
BRIEF: 1000, // Very brief toasts ("Sent..." messages)
|
||||
SHORT: 2000, // Short notifications (clipboard copies, quick confirmations)
|
||||
STANDARD: 3000, // Standard notifications (success messages, general info)
|
||||
LONG: 5000, // Longer notifications (errors, warnings, important info)
|
||||
VERY_LONG: 7000, // Very long notifications (complex operations)
|
||||
MODAL: -1, // Modal confirmations (no auto-dismiss)
|
||||
BRIEF: 1000, // Very brief toasts ("Sent..." messages)
|
||||
SHORT: 2000, // Short notifications (clipboard copies, quick confirmations)
|
||||
STANDARD: 3000, // Standard notifications (success messages, general info)
|
||||
LONG: 5000, // Longer notifications (errors, warnings, important info)
|
||||
VERY_LONG: 7000, // Very long notifications (complex operations)
|
||||
MODAL: -1, // Modal confirmations (no auto-dismiss)
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@@ -61,8 +61,10 @@ export const NOTIFICATION_MESSAGES = {
|
||||
SENT_BRIEF: "Sent...",
|
||||
CONFIRMATION_SUBMITTED: "Confirmation submitted.",
|
||||
ALREADY_CONFIRMED: "You already confirmed this claim.",
|
||||
CANNOT_CONFIRM_ISSUER: "You cannot confirm this because you issued this claim.",
|
||||
CANNOT_CONFIRM_HIDDEN: "You cannot confirm this because some people are hidden.",
|
||||
CANNOT_CONFIRM_ISSUER:
|
||||
"You cannot confirm this because you issued this claim.",
|
||||
CANNOT_CONFIRM_HIDDEN:
|
||||
"You cannot confirm this because some people are hidden.",
|
||||
NOT_REGISTERED: "Someone needs to register you before you can confirm.",
|
||||
NOT_A_GIVE: "This is not a giving action to confirm.",
|
||||
} as const;
|
||||
@@ -70,143 +72,195 @@ export const NOTIFICATION_MESSAGES = {
|
||||
/**
|
||||
* Creates a notification helper with utility methods
|
||||
*/
|
||||
export function createNotificationHelper(notifyFn: (notification: NotificationIface, timeout?: number) => void): NotificationHelper {
|
||||
export function createNotificationHelper(
|
||||
notifyFn: (notification: NotificationIface, timeout?: number) => void,
|
||||
): NotificationHelper {
|
||||
return {
|
||||
notify: notifyFn,
|
||||
|
||||
|
||||
// Success notifications
|
||||
success: (text: string, timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: NOTIFICATION_TITLES.SUCCESS,
|
||||
text,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: NOTIFICATION_TITLES.SUCCESS,
|
||||
text,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
// Error notifications
|
||||
error: (text: string, timeout = NOTIFICATION_TIMEOUTS.LONG) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: NOTIFICATION_TITLES.ERROR,
|
||||
text,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: NOTIFICATION_TITLES.ERROR,
|
||||
text,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
// Warning notifications
|
||||
warning: (text: string, timeout = NOTIFICATION_TIMEOUTS.LONG) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: NOTIFICATION_TITLES.WARNING,
|
||||
text,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "warning",
|
||||
title: NOTIFICATION_TITLES.WARNING,
|
||||
text,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
// Info notifications
|
||||
info: (text: string, timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.INFO,
|
||||
text,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.INFO,
|
||||
text,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
// Toast notifications (brief)
|
||||
toast: (title: string, text?: string, timeout = NOTIFICATION_TIMEOUTS.BRIEF) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title,
|
||||
text,
|
||||
}, timeout);
|
||||
toast: (
|
||||
title: string,
|
||||
text?: string,
|
||||
timeout = NOTIFICATION_TIMEOUTS.BRIEF,
|
||||
) => {
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title,
|
||||
text,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
// Clipboard copy notifications
|
||||
copied: (item: string, timeout = NOTIFICATION_TIMEOUTS.SHORT) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: NOTIFICATION_TITLES.COPIED,
|
||||
text: NOTIFICATION_MESSAGES.CLIPBOARD_COPIED(item),
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: NOTIFICATION_TITLES.COPIED,
|
||||
text: NOTIFICATION_MESSAGES.CLIPBOARD_COPIED(item),
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
// Sent brief notification
|
||||
sent: (timeout = NOTIFICATION_TIMEOUTS.BRIEF) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: NOTIFICATION_TITLES.SENT,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: NOTIFICATION_TITLES.SENT,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
// Confirmation modal
|
||||
confirm: (text: string, onYes: () => Promise<void>, timeout = NOTIFICATION_TIMEOUTS.MODAL) => {
|
||||
notifyFn({
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: NOTIFICATION_TITLES.CONFIRM,
|
||||
text,
|
||||
onYes,
|
||||
}, timeout);
|
||||
confirm: (
|
||||
text: string,
|
||||
onYes: () => Promise<void>,
|
||||
timeout = NOTIFICATION_TIMEOUTS.MODAL,
|
||||
) => {
|
||||
notifyFn(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: NOTIFICATION_TITLES.CONFIRM,
|
||||
text,
|
||||
onYes,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
// Standard confirmation messages
|
||||
confirmationSubmitted: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: NOTIFICATION_TITLES.SUCCESS,
|
||||
text: NOTIFICATION_MESSAGES.CONFIRMATION_SUBMITTED,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: NOTIFICATION_TITLES.SUCCESS,
|
||||
text: NOTIFICATION_MESSAGES.CONFIRMATION_SUBMITTED,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
alreadyConfirmed: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.ALREADY_CONFIRMED,
|
||||
text: NOTIFICATION_MESSAGES.ALREADY_CONFIRMED,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.ALREADY_CONFIRMED,
|
||||
text: NOTIFICATION_MESSAGES.ALREADY_CONFIRMED,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
cannotConfirmIssuer: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.CANNOT_CONFIRM,
|
||||
text: NOTIFICATION_MESSAGES.CANNOT_CONFIRM_ISSUER,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.CANNOT_CONFIRM,
|
||||
text: NOTIFICATION_MESSAGES.CANNOT_CONFIRM_ISSUER,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
cannotConfirmHidden: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.CANNOT_CONFIRM,
|
||||
text: NOTIFICATION_MESSAGES.CANNOT_CONFIRM_HIDDEN,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.CANNOT_CONFIRM,
|
||||
text: NOTIFICATION_MESSAGES.CANNOT_CONFIRM_HIDDEN,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
notRegistered: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.NOT_REGISTERED,
|
||||
text: NOTIFICATION_MESSAGES.NOT_REGISTERED,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.NOT_REGISTERED,
|
||||
text: NOTIFICATION_MESSAGES.NOT_REGISTERED,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
|
||||
notAGive: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
|
||||
notifyFn({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.INFO,
|
||||
text: NOTIFICATION_MESSAGES.NOT_A_GIVE,
|
||||
}, timeout);
|
||||
notifyFn(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: NOTIFICATION_TITLES.INFO,
|
||||
text: NOTIFICATION_MESSAGES.NOT_A_GIVE,
|
||||
},
|
||||
timeout,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -217,7 +271,8 @@ export function createNotificationHelper(notifyFn: (notification: NotificationIf
|
||||
export const NotificationMixin = {
|
||||
computed: {
|
||||
$notifyHelper() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return createNotificationHelper((this as any).$notify);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { NotificationIface } from '../constants/app';
|
||||
import { NotificationIface } from "../constants/app";
|
||||
|
||||
/**
|
||||
* Simple notification utility functions
|
||||
* Provides the most concise API for common notification patterns
|
||||
*/
|
||||
|
||||
export type NotifyFunction = (notification: NotificationIface, timeout?: number) => void;
|
||||
export type NotifyFunction = (
|
||||
notification: NotificationIface,
|
||||
timeout?: number,
|
||||
) => void;
|
||||
|
||||
// Standard timeouts
|
||||
export const TIMEOUTS = {
|
||||
BRIEF: 1000, // Very brief toasts ("Sent..." messages)
|
||||
SHORT: 2000, // Short notifications (clipboard copies, quick confirmations)
|
||||
STANDARD: 3000, // Standard notifications (success messages, general info)
|
||||
LONG: 5000, // Longer notifications (errors, warnings, important info)
|
||||
VERY_LONG: 7000, // Very long notifications (complex operations)
|
||||
MODAL: -1, // Modal confirmations (no auto-dismiss)
|
||||
BRIEF: 1000, // Very brief toasts ("Sent..." messages)
|
||||
SHORT: 2000, // Short notifications (clipboard copies, quick confirmations)
|
||||
STANDARD: 3000, // Standard notifications (success messages, general info)
|
||||
LONG: 5000, // Longer notifications (errors, warnings, important info)
|
||||
VERY_LONG: 7000, // Very long notifications (complex operations)
|
||||
MODAL: -1, // Modal confirmations (no auto-dismiss)
|
||||
} as const;
|
||||
|
||||
/**
|
||||
@@ -23,114 +26,191 @@ export const TIMEOUTS = {
|
||||
export function createNotifyHelpers(notify: NotifyFunction) {
|
||||
return {
|
||||
// Success notifications
|
||||
success: (text: string, timeout?: number) =>
|
||||
notify({ group: "alert", type: "success", title: "Success", text }, timeout || TIMEOUTS.STANDARD),
|
||||
success: (text: string, timeout?: number) =>
|
||||
notify(
|
||||
{ group: "alert", type: "success", title: "Success", text },
|
||||
timeout || TIMEOUTS.STANDARD,
|
||||
),
|
||||
|
||||
// Error notifications
|
||||
error: (text: string, timeout?: number) =>
|
||||
notify({ group: "alert", type: "danger", title: "Error", text }, timeout || TIMEOUTS.LONG),
|
||||
error: (text: string, timeout?: number) =>
|
||||
notify(
|
||||
{ group: "alert", type: "danger", title: "Error", text },
|
||||
timeout || TIMEOUTS.LONG,
|
||||
),
|
||||
|
||||
// Warning notifications
|
||||
warning: (text: string, timeout?: number) =>
|
||||
notify({ group: "alert", type: "warning", title: "Warning", text }, timeout || TIMEOUTS.LONG),
|
||||
warning: (text: string, timeout?: number) =>
|
||||
notify(
|
||||
{ group: "alert", type: "warning", title: "Warning", text },
|
||||
timeout || TIMEOUTS.LONG,
|
||||
),
|
||||
|
||||
// Info notifications
|
||||
info: (text: string, timeout?: number) =>
|
||||
notify({ group: "alert", type: "info", title: "Info", text }, timeout || TIMEOUTS.STANDARD),
|
||||
info: (text: string, timeout?: number) =>
|
||||
notify(
|
||||
{ group: "alert", type: "info", title: "Info", text },
|
||||
timeout || TIMEOUTS.STANDARD,
|
||||
),
|
||||
|
||||
// Toast notifications (brief)
|
||||
toast: (title: string, text?: string, timeout?: number) =>
|
||||
notify({ group: "alert", type: "toast", title, text }, timeout || TIMEOUTS.BRIEF),
|
||||
toast: (title: string, text?: string, timeout?: number) =>
|
||||
notify(
|
||||
{ group: "alert", type: "toast", title, text },
|
||||
timeout || TIMEOUTS.BRIEF,
|
||||
),
|
||||
|
||||
// Clipboard copy notifications
|
||||
copied: (item: string, timeout?: number) =>
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: "Copied",
|
||||
text: `${item} was copied to the clipboard.`
|
||||
}, timeout || TIMEOUTS.SHORT),
|
||||
copied: (item: string, timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "toast",
|
||||
title: "Copied",
|
||||
text: `${item} was copied to the clipboard.`,
|
||||
},
|
||||
timeout || TIMEOUTS.SHORT,
|
||||
),
|
||||
|
||||
// Sent brief notification
|
||||
sent: (timeout?: number) =>
|
||||
notify({ group: "alert", type: "toast", title: "Sent..." }, timeout || TIMEOUTS.BRIEF),
|
||||
sent: (timeout?: number) =>
|
||||
notify(
|
||||
{ group: "alert", type: "toast", title: "Sent..." },
|
||||
timeout || TIMEOUTS.BRIEF,
|
||||
),
|
||||
|
||||
// Confirmation modal
|
||||
confirm: (text: string, onYes: () => Promise<void>, timeout?: number) =>
|
||||
notify({
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Confirm",
|
||||
text,
|
||||
onYes
|
||||
}, timeout || TIMEOUTS.MODAL),
|
||||
confirm: (text: string, onYes: () => Promise<void>, timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Confirm",
|
||||
text,
|
||||
onYes,
|
||||
},
|
||||
timeout || TIMEOUTS.MODAL,
|
||||
),
|
||||
|
||||
// Standard confirmation messages
|
||||
confirmationSubmitted: (timeout?: number) =>
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text: "Confirmation submitted."
|
||||
}, timeout || TIMEOUTS.STANDARD),
|
||||
confirmationSubmitted: (timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text: "Confirmation submitted.",
|
||||
},
|
||||
timeout || TIMEOUTS.STANDARD,
|
||||
),
|
||||
|
||||
// Common error patterns
|
||||
genericError: (timeout?: number) =>
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "Something went wrong."
|
||||
}, timeout || TIMEOUTS.LONG),
|
||||
genericError: (timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "Something went wrong.",
|
||||
},
|
||||
timeout || TIMEOUTS.LONG,
|
||||
),
|
||||
|
||||
// Common success patterns
|
||||
genericSuccess: (timeout?: number) =>
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text: "Operation completed successfully."
|
||||
}, timeout || TIMEOUTS.STANDARD),
|
||||
genericSuccess: (timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Success",
|
||||
text: "Operation completed successfully.",
|
||||
},
|
||||
timeout || TIMEOUTS.STANDARD,
|
||||
),
|
||||
|
||||
// Common confirmation patterns
|
||||
alreadyConfirmed: (timeout?: number) =>
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Already Confirmed",
|
||||
text: "You already confirmed this claim."
|
||||
}, timeout || TIMEOUTS.STANDARD),
|
||||
alreadyConfirmed: (timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Already Confirmed",
|
||||
text: "You already confirmed this claim.",
|
||||
},
|
||||
timeout || TIMEOUTS.STANDARD,
|
||||
),
|
||||
|
||||
cannotConfirmIssuer: (timeout?: number) =>
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Cannot Confirm",
|
||||
text: "You cannot confirm this because you issued this claim."
|
||||
}, timeout || TIMEOUTS.STANDARD),
|
||||
cannotConfirmIssuer: (timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Cannot Confirm",
|
||||
text: "You cannot confirm this because you issued this claim.",
|
||||
},
|
||||
timeout || TIMEOUTS.STANDARD,
|
||||
),
|
||||
|
||||
cannotConfirmHidden: (timeout?: number) =>
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Cannot Confirm",
|
||||
text: "You cannot confirm this because some people are hidden."
|
||||
}, timeout || TIMEOUTS.STANDARD),
|
||||
cannotConfirmHidden: (timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Cannot Confirm",
|
||||
text: "You cannot confirm this because some people are hidden.",
|
||||
},
|
||||
timeout || TIMEOUTS.STANDARD,
|
||||
),
|
||||
|
||||
notRegistered: (timeout?: number) =>
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Not Registered",
|
||||
text: "Someone needs to register you before you can confirm."
|
||||
}, timeout || TIMEOUTS.STANDARD),
|
||||
notRegistered: (timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Not Registered",
|
||||
text: "Someone needs to register you before you can confirm.",
|
||||
},
|
||||
timeout || TIMEOUTS.STANDARD,
|
||||
),
|
||||
|
||||
notAGive: (timeout?: number) =>
|
||||
notify({
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Info",
|
||||
text: "This is not a giving action to confirm."
|
||||
}, timeout || TIMEOUTS.STANDARD),
|
||||
notAGive: (timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Info",
|
||||
text: "This is not a giving action to confirm.",
|
||||
},
|
||||
timeout || TIMEOUTS.STANDARD,
|
||||
),
|
||||
|
||||
// Notification-off modal (for turning off notifications)
|
||||
notificationOff: (
|
||||
title: string,
|
||||
callback: (success: boolean) => Promise<void>,
|
||||
timeout?: number,
|
||||
) =>
|
||||
notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "notification-off",
|
||||
title,
|
||||
text: "", // unused, only here to satisfy type check
|
||||
callback,
|
||||
},
|
||||
timeout || TIMEOUTS.MODAL,
|
||||
),
|
||||
|
||||
// Download notifications
|
||||
downloadStarted: (format: string = "Dexie", timeout?: number) =>
|
||||
notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Download Started",
|
||||
text: `See your downloads directory for the backup. It is in the ${format} format.`,
|
||||
},
|
||||
timeout || TIMEOUTS.MODAL,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -732,7 +732,9 @@ export default class ClaimView extends Vue {
|
||||
"Error retrieving all account DIDs on home page:" + error,
|
||||
true,
|
||||
);
|
||||
this.notify.error("See the Help page for problems with your personal data.");
|
||||
this.notify.error(
|
||||
"See the Help page for problems with your personal data.",
|
||||
);
|
||||
}
|
||||
|
||||
const claimId = this.$route.params.id as string;
|
||||
@@ -875,7 +877,10 @@ export default class ClaimView extends Vue {
|
||||
await this.$logError(
|
||||
"Error retrieving claim: " + JSON.stringify(serverError),
|
||||
);
|
||||
this.notify.error("Something went wrong retrieving claim data.", TIMEOUTS.STANDARD);
|
||||
this.notify.error(
|
||||
"Something went wrong retrieving claim data.",
|
||||
TIMEOUTS.STANDARD,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -932,7 +937,10 @@ export default class ClaimView extends Vue {
|
||||
" if they can find out more and make an introduction: " +
|
||||
" send them this page and see if they can make a connection for you.";
|
||||
} else {
|
||||
this.notify.error("Something went wrong retrieving that claim.", TIMEOUTS.LONG);
|
||||
this.notify.error(
|
||||
"Something went wrong retrieving that claim.",
|
||||
TIMEOUTS.LONG,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -942,7 +950,7 @@ export default class ClaimView extends Vue {
|
||||
"Do you personally confirm that this is true?",
|
||||
async () => {
|
||||
await this.confirmClaim();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1051,7 +1059,10 @@ export default class ClaimView extends Vue {
|
||||
await this.$logError(
|
||||
"Unrecognized claim type for edit: " + this.veriClaim.claimType,
|
||||
);
|
||||
this.notify.error("This is an unrecognized claim type.", TIMEOUTS.STANDARD);
|
||||
this.notify.error(
|
||||
"This is an unrecognized claim type.",
|
||||
TIMEOUTS.STANDARD,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user