Browse Source

Add notification utility helpers and update PlatformServiceMixin

Created notification utility approaches to consolidate verbose $notify calls:
- Simple function utility (src/utils/notify.ts) - recommended approach
- Vue 3 composable (src/composables/useNotifications.ts)
- Utility class with mixin (src/utils/notificationUtils.ts)

Updated ClaimView.vue to demonstrate usage, reducing notification code by ~70%.
Enhanced PlatformServiceMixin with improved caching and database methods.
Updated ShareMyContactInfoView.vue with mixin improvements.
Provides consistent timeouts, standardized patterns, and type safety.
Ready for migration alongside mixin updates.
pull/142/head
Matthew Raymer 1 day ago
parent
commit
b2a2387f1a
  1. 140
      src/composables/useNotifications.ts
  2. 16
      src/utils/PlatformServiceMixin.ts
  3. 223
      src/utils/notificationUtils.ts
  4. 136
      src/utils/notify.ts
  5. 138
      src/views/ClaimView.vue
  6. 152
      src/views/ShareMyContactInfoView.vue

140
src/composables/useNotifications.ts

@ -0,0 +1,140 @@
import { inject } from 'vue';
import { NotificationIface } from '../constants/app';
/**
* Vue 3 composable for notifications
* Provides a concise API for common notification patterns
*/
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)
} as const;
export function useNotifications() {
// Inject the notify function from the app
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');
}
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);
},
};
}

16
src/utils/PlatformServiceMixin.ts

@ -545,8 +545,8 @@ export const PlatformServiceMixin = {
/**
* Load all contacts with caching - $contacts()
* Ultra-concise shortcut with 60s TTL for performance
* @returns Cached mapped array of all contacts
* Contacts are cached for 60 seconds for performance
* @returns Promise<Contact[]> Array of contact objects
*/
async $contacts(): Promise<Contact[]> {
const cacheKey = "contacts_all";
@ -565,6 +565,16 @@ export const PlatformServiceMixin = {
);
},
/**
* Get total contact count - $contactCount()
* Ultra-concise shortcut for getting number of contacts
* @returns Promise<number> Total number of contacts
*/
async $contactCount(): Promise<number> {
const countRow = await this.$one("SELECT COUNT(*) FROM contacts");
return (countRow?.[0] as number) || 0;
},
/**
* Load settings with optional defaults WITHOUT caching - $settings()
* Settings are loaded fresh every time for immediate consistency
@ -1136,6 +1146,7 @@ export interface IPlatformServiceMixin {
$insertContact(contact: Partial<Contact>): Promise<boolean>;
$updateContact(did: string, changes: Partial<Contact>): Promise<boolean>;
$getAllContacts(): Promise<Contact[]>;
$contactCount(): Promise<number>;
$insertEntity(
tableName: string,
entity: Record<string, unknown>,
@ -1211,6 +1222,7 @@ declare module "@vue/runtime-core" {
// Specialized shortcuts - contacts cached, settings fresh
$contacts(): Promise<Contact[]>;
$contactCount(): Promise<number>;
$settings(defaults?: Settings): Promise<Settings>;
$accountSettings(did?: string, defaults?: Settings): Promise<Settings>;

223
src/utils/notificationUtils.ts

@ -0,0 +1,223 @@
import { NotificationIface } from "../constants/app";
/**
* Notification utility methods to reduce code duplication
* and provide consistent notification patterns across the app
*/
export interface NotificationHelper {
notify: (notification: NotificationIface, timeout?: number) => void;
success: (text: string, timeout?: number) => void;
error: (text: string, timeout?: number) => void;
warning: (text: string, timeout?: number) => void;
info: (text: string, timeout?: number) => void;
toast: (title: string, text?: string, timeout?: number) => void;
copied: (item: string, timeout?: number) => void;
sent: (timeout?: number) => void;
confirm: (text: string, onYes: () => Promise<void>, timeout?: number) => void;
confirmationSubmitted: (timeout?: number) => void;
alreadyConfirmed: (timeout?: number) => void;
cannotConfirmIssuer: (timeout?: number) => void;
cannotConfirmHidden: (timeout?: number) => void;
notRegistered: (timeout?: number) => void;
notAGive: (timeout?: number) => void;
}
/**
* 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)
} as const;
/**
* Standard notification titles
*/
export const NOTIFICATION_TITLES = {
SUCCESS: "Success",
ERROR: "Error",
WARNING: "Warning",
INFO: "Info",
COPIED: "Copied",
SENT: "Sent...",
CONFIRM: "Confirm",
NOT_REGISTERED: "Not Registered",
ALREADY_CONFIRMED: "Already Confirmed",
CANNOT_CONFIRM: "Cannot Confirm",
} as const;
/**
* Standard notification messages
*/
export const NOTIFICATION_MESSAGES = {
GENERIC_ERROR: "Something went wrong.",
GENERIC_SUCCESS: "Operation completed successfully.",
CLIPBOARD_COPIED: (item: string) => `${item} was copied to the clipboard.`,
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.",
NOT_REGISTERED: "Someone needs to register you before you can confirm.",
NOT_A_GIVE: "This is not a giving action to confirm.",
} as const;
/**
* Creates a notification helper with utility methods
*/
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);
},
// Error notifications
error: (text: string, timeout = NOTIFICATION_TIMEOUTS.LONG) => {
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);
},
// Info notifications
info: (text: string, timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
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);
},
// 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);
},
// Sent brief notification
sent: (timeout = NOTIFICATION_TIMEOUTS.BRIEF) => {
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);
},
// Standard confirmation messages
confirmationSubmitted: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
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);
},
cannotConfirmIssuer: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
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);
},
notRegistered: (timeout = NOTIFICATION_TIMEOUTS.STANDARD) => {
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);
},
};
}
/**
* Vue mixin to add notification helpers to components
*/
export const NotificationMixin = {
computed: {
$notifyHelper() {
return createNotificationHelper((this as any).$notify);
},
},
};

136
src/utils/notify.ts

@ -0,0 +1,136 @@
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;
// 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)
} as const;
/**
* Create notification helpers for a given notify function
*/
export function createNotifyHelpers(notify: NotifyFunction) {
return {
// Success notifications
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),
// Warning notifications
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),
// Toast notifications (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),
// Sent brief notification
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),
// Standard confirmation messages
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),
// Common success patterns
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),
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),
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),
};
}

138
src/views/ClaimView.vue

@ -531,6 +531,7 @@ import {
} from "../interfaces";
import * as libsUtil from "../libs/util";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
@Component({
components: { GiftedDialog, QuickNav },
@ -577,6 +578,9 @@ export default class ClaimView extends Vue {
libsUtil = libsUtil;
serverUtil = serverUtil;
// Add notification helpers
private notify = createNotifyHelpers(this.$notify);
// =================================================
// COMPUTED PROPERTIES
// =================================================
@ -728,30 +732,14 @@ export default class ClaimView extends Vue {
"Error retrieving all account DIDs on home page:" + error,
true,
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Loading Profile",
text: "See the Help page for problems with your personal data.",
},
5000,
);
this.notify.error("See the Help page for problems with your personal data.");
}
const claimId = this.$route.params.id as string;
if (claimId) {
await this.loadClaim(claimId, this.activeDid);
} else {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "No claim ID was provided.",
},
5000,
);
this.notify.error("No claim ID was provided.");
}
this.windowDeepLink = `${APP_SERVER}/deep-link/claim/${claimId}`;
@ -803,15 +791,7 @@ export default class ClaimView extends Vue {
} else {
// actually, axios typically throws an error so we never get here
await this.$logError("Error getting claim: " + JSON.stringify(resp));
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "There was a problem retrieving that claim.",
},
5000,
);
this.notify.error("There was a problem retrieving that claim.");
return;
}
@ -854,15 +834,7 @@ export default class ClaimView extends Vue {
await this.$logError(
"Error getting give providers: " + JSON.stringify(giveResp),
);
this.$notify(
{
group: "alert",
type: "warning",
title: "Error",
text: "Got error retrieving linked provider data.",
},
5000,
);
this.notify.warning("Got error retrieving linked provider data.");
}
} else if (this.veriClaim.claimType === "Offer") {
const offerUrl =
@ -879,15 +851,7 @@ export default class ClaimView extends Vue {
await this.$logError(
"Error getting detailed offer info: " + JSON.stringify(offerResp),
);
this.$notify(
{
group: "alert",
type: "warning",
title: "Error",
text: "Got error retrieving linked offer data.",
},
5000,
);
this.notify.warning("Got error retrieving linked offer data.");
}
}
@ -911,15 +875,7 @@ export default class ClaimView extends Vue {
await this.$logError(
"Error retrieving claim: " + JSON.stringify(serverError),
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "Something went wrong retrieving claim data.",
},
3000,
);
this.notify.error("Something went wrong retrieving claim data.", TIMEOUTS.STANDARD);
}
}
@ -938,15 +894,7 @@ export default class ClaimView extends Vue {
await this.$logError(
"Error getting full claim: " + JSON.stringify(resp),
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "There was a problem getting that claim.",
},
5000,
);
this.notify.error("There was a problem getting that claim.");
}
} catch (error: unknown) {
await this.$logError(
@ -984,31 +932,17 @@ 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(
{
group: "alert",
type: "danger",
title: "Error",
text: "Something went wrong retrieving that claim.",
},
5000,
);
this.notify.error("Something went wrong retrieving that claim.", TIMEOUTS.LONG);
}
}
}
confirmConfirmClaim() {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Confirm",
text: "Do you personally confirm that this is true?",
onYes: async () => {
this.notify.confirm(
"Do you personally confirm that this is true?",
async () => {
await this.confirmClaim();
},
},
-1,
}
);
}
@ -1036,28 +970,12 @@ export default class ClaimView extends Vue {
this.axios,
);
if (result.success) {
this.$notify(
{
group: "alert",
type: "success",
title: "Success",
text: "Confirmation submitted.",
},
5000,
);
this.notify.confirmationSubmitted();
} else {
await this.$logError(
"Got error submitting the confirmation: " + JSON.stringify(result),
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "There was a problem submitting the confirmation.",
},
5000,
);
this.notify.error("There was a problem submitting the confirmation.");
}
}
@ -1089,15 +1007,7 @@ export default class ClaimView extends Vue {
useClipboard()
.copy(text)
.then(() => {
this.$notify(
{
group: "alert",
type: "toast",
title: "Copied",
text: (name || "That") + " was copied to the clipboard.",
},
2000,
);
this.notify.copied(name || "That");
});
}
@ -1141,15 +1051,7 @@ export default class ClaimView extends Vue {
await this.$logError(
"Unrecognized claim type for edit: " + this.veriClaim.claimType,
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "This is an unrecognized claim type.",
},
3000,
);
this.notify.error("This is an unrecognized claim type.", TIMEOUTS.STANDARD);
}
}
}

152
src/views/ShareMyContactInfoView.vue

@ -8,12 +8,13 @@
<div>
<!-- Back -->
<div class="text-lg text-center font-light relative px-7">
<h1
<button
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
aria-label="Go back"
@click="$router.back()"
>
<font-awesome icon="chevron-left" class="fa-fw" />
</h1>
</button>
</div>
<!-- Heading -->
@ -24,10 +25,12 @@
<div class="flex justify-center mt-8">
<button
class="block w-fit text-center text-lg font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md"
class="block w-fit text-center text-lg font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-2 py-3 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="isLoading"
aria-label="Copy contact information to clipboard"
@click="onClickShare()"
>
Copy to Clipboard
{{ isLoading ? "Copying..." : "Copy to Clipboard" }}
</button>
</div>
<div class="ml-12">
@ -43,56 +46,109 @@
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { useClipboard } from "@vueuse/core";
import QuickNav from "../components/QuickNav.vue";
import TopMessage from "../components/TopMessage.vue";
import { NotificationIface, APP_SERVER } from "../constants/app";
import * as databaseUtil from "../db/databaseUtil";
import { NotificationIface } from "../constants/app";
import { retrieveFullyDecryptedAccount } from "../libs/util";
import { generateEndorserJwtUrlForAccount } from "../libs/endorserServer";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { Settings } from "@/db/tables/settings";
import { Account } from "@/db/tables/accounts";
// Constants for magic numbers
const NOTIFICATION_TIMEOUTS = {
COPY_SUCCESS: 5000,
SHARE_CONTACTS: 10000,
ERROR: 5000,
} as const;
const DELAYS = {
SHARE_CONTACTS_DELAY: 3000,
} as const;
@Component({
mixins: [PlatformServiceMixin],
components: { QuickNav, TopMessage },
})
export default class ShareMyContactInfoView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
$router!: Router;
mounted() {
logger.log("APP_SERVER in mounted:", APP_SERVER);
// Component state
isLoading = false;
/**
* Main share functionality - orchestrates the contact sharing process
*/
async onClickShare(): Promise<void> {
this.isLoading = true;
try {
const settings = await this.$settings();
const account = await this.retrieveAccount(settings);
if (!account) {
this.showAccountError();
return;
}
async onClickShare() {
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
const message = await this.generateContactMessage(settings, account);
await this.copyToClipboard(message);
await this.showSuccessNotifications();
this.navigateToContacts();
} catch (error) {
await this.$logError(`Error sharing contact info: ${error}`);
this.showGenericError();
} finally {
this.isLoading = false;
}
}
/**
* Retrieve the fully decrypted account for the active DID
*/
private async retrieveAccount(
settings: Settings,
): Promise<Account | undefined> {
const activeDid = settings.activeDid || "";
if (!activeDid) {
return undefined;
}
return await retrieveFullyDecryptedAccount(activeDid);
}
/**
* Generate the contact message URL for sharing
*/
private async generateContactMessage(
settings: Settings,
account: Account,
): Promise<string> {
const givenName = settings.firstName || "";
const isRegistered = !!settings.isRegistered;
const profileImageUrl = settings.profileImageUrl || "";
const account = await retrieveFullyDecryptedAccount(activeDid);
const platformService = PlatformServiceFactory.getInstance();
const contactQueryResult = await platformService.dbQuery(
"SELECT COUNT(*) FROM contacts",
);
const numContacts =
(databaseUtil.mapQueryResultToValues(
contactQueryResult,
)?.[0]?.[0] as number) || 0;
if (account) {
const message = await generateEndorserJwtUrlForAccount(
return await generateEndorserJwtUrlForAccount(
account,
isRegistered,
givenName,
profileImageUrl,
true,
);
useClipboard()
.copy(message)
.then(() => {
}
/**
* Copy the contact message to clipboard
*/
private async copyToClipboard(message: string): Promise<void> {
const { useClipboard } = await import("@vueuse/core");
await useClipboard().copy(message);
}
/**
* Show success notifications after copying
*/
private async showSuccessNotifications(): Promise<void> {
this.$notify(
{
group: "alert",
@ -100,8 +156,10 @@ export default class ShareMyContactInfoView extends Vue {
title: "Copied",
text: "Your contact info was copied to the clipboard. Have them click on it, or paste it in the box on their 'Contacts' screen.",
},
5000,
NOTIFICATION_TIMEOUTS.COPY_SUCCESS,
);
const numContacts = await this.$contactCount();
if (numContacts > 0) {
setTimeout(() => {
this.$notify(
@ -111,13 +169,23 @@ export default class ShareMyContactInfoView extends Vue {
title: "Share Other Contacts",
text: "You may want to share some of your contacts with them. Select them below to copy and send.",
},
10000,
NOTIFICATION_TIMEOUTS.SHARE_CONTACTS,
);
}, 3000);
}, DELAYS.SHARE_CONTACTS_DELAY);
}
});
}
/**
* Navigate to contacts page
*/
private navigateToContacts(): void {
this.$router.push({ name: "contacts" });
} else {
}
/**
* Show account not found error
*/
private showAccountError(): void {
this.$notify(
{
group: "alert",
@ -125,9 +193,23 @@ export default class ShareMyContactInfoView extends Vue {
title: "Error",
text: "No account was found for the active DID.",
},
5000,
NOTIFICATION_TIMEOUTS.ERROR,
);
}
/**
* Show generic error notification
*/
private showGenericError(): void {
this.$notify(
{
group: "alert",
type: "error",
title: "Error",
text: "There was a problem sharing your contact information. Please try again.",
},
NOTIFICATION_TIMEOUTS.ERROR,
);
}
}
</script>

Loading…
Cancel
Save