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.
This commit is contained in:
Matthew Raymer
2025-07-05 11:37:20 +00:00
parent bbdb962d4d
commit 08c2113504
6 changed files with 683 additions and 188 deletions

View File

@@ -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,91 +46,170 @@
<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;
}
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;
}
}
async onClickShare() {
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
/**
* 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",
return await generateEndorserJwtUrlForAccount(
account,
isRegistered,
givenName,
profileImageUrl,
true,
);
const numContacts =
(databaseUtil.mapQueryResultToValues(
contactQueryResult,
)?.[0]?.[0] as number) || 0;
}
if (account) {
const message = await generateEndorserJwtUrlForAccount(
account,
isRegistered,
givenName,
profileImageUrl,
true,
);
useClipboard()
.copy(message)
.then(() => {
this.$notify(
{
group: "alert",
type: "info",
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,
);
if (numContacts > 0) {
setTimeout(() => {
this.$notify(
{
group: "alert",
type: "success",
title: "Share Other Contacts",
text: "You may want to share some of your contacts with them. Select them below to copy and send.",
},
10000,
);
}, 3000);
}
});
this.$router.push({ name: "contacts" });
} else {
this.$notify(
{
group: "alert",
type: "error",
title: "Error",
text: "No account was found for the active DID.",
},
5000,
);
/**
* 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",
type: "info",
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.",
},
NOTIFICATION_TIMEOUTS.COPY_SUCCESS,
);
const numContacts = await this.$contactCount();
if (numContacts > 0) {
setTimeout(() => {
this.$notify(
{
group: "alert",
type: "success",
title: "Share Other Contacts",
text: "You may want to share some of your contacts with them. Select them below to copy and send.",
},
NOTIFICATION_TIMEOUTS.SHARE_CONTACTS,
);
}, DELAYS.SHARE_CONTACTS_DELAY);
}
}
/**
* Navigate to contacts page
*/
private navigateToContacts(): void {
this.$router.push({ name: "contacts" });
}
/**
* Show account not found error
*/
private showAccountError(): void {
this.$notify(
{
group: "alert",
type: "error",
title: "Error",
text: "No account was found for the active DID.",
},
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>