refactor: centralize QR navigation logic and add export prompt after contact addition

- Create QRNavigationService to handle platform-specific QR routing
- Remove direct Capacitor imports from ContactsView, ProjectsView, HelpView
- Replace duplicated QR routing logic with centralized service calls
- Update HelpView template to use platform service methods (isCapacitor, capabilities)
- Add export data prompt after successfully adding a contact
- Add NOTIFY_EXPORT_DATA_PROMPT notification constant
- Implement exportContactData() method with platform service integration
- Fix TypeScript compatibility for Vue Router route parameters
- Maintain consistent QR navigation behavior across all views

Eliminates code duplication and improves platform abstraction by using
PlatformService instead of direct Capacitor references. Enhances user
experience with automatic export prompts for data backup.
This commit is contained in:
Matthew Raymer
2025-07-30 12:47:55 +00:00
parent ca828d45a6
commit 6007bc34e4
5 changed files with 194 additions and 25 deletions

View File

@@ -127,7 +127,7 @@ import * as R from "ramda";
import { Component, Vue } from "vue-facing-decorator";
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import { useClipboard } from "@vueuse/core";
import { Capacitor } from "@capacitor/core";
// Capacitor import removed - using PlatformService instead
import QuickNav from "../components/QuickNav.vue";
import EntityIcon from "../components/EntityIcon.vue";
@@ -161,13 +161,17 @@ import { GiveSummaryRecord } from "@/interfaces/records";
import { UserInfo } from "@/interfaces/common";
import { VerifiableCredential } from "@/interfaces/claims-result";
import * as libsUtil from "../libs/util";
import { generateSaveAndActivateIdentity } from "../libs/util";
import {
generateSaveAndActivateIdentity,
contactsToExportJson,
} from "../libs/util";
import { logger } from "../utils/logger";
// No longer needed - using PlatformServiceMixin methods
// import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import { APP_SERVER } from "@/constants/app";
import { QRNavigationService } from "@/services/QRNavigationService";
import {
NOTIFY_CONTACT_NO_INFO,
NOTIFY_CONTACTS_ADD_ERROR,
@@ -193,6 +197,7 @@ import {
NOTIFY_REGISTRATION_ERROR_FALLBACK,
NOTIFY_REGISTRATION_ERROR_GENERIC,
NOTIFY_VISIBILITY_ERROR_FALLBACK,
NOTIFY_EXPORT_DATA_PROMPT,
getRegisterPersonSuccessMessage,
getVisibilitySuccessMessage,
getGivesRetrievalErrorMessage,
@@ -780,6 +785,9 @@ export default class ContactsView extends Vue {
// Show success notification
this.notify.success(addedMessage);
// Show export data prompt after successful contact addition
await this.showExportDataPrompt();
} catch (err) {
this.handleContactAddError(err);
}
@@ -1243,19 +1251,75 @@ export default class ContactsView extends Vue {
/**
* Handle QR code button click - route to appropriate scanner
* Uses native scanner on mobile platforms, web scanner otherwise
* Uses QRNavigationService to determine scanner type and route
*/
public handleQRCodeClick() {
this.$logAndConsole(
"[ContactsView] handleQRCodeClick method called",
false,
);
if (Capacitor.isNativePlatform()) {
this.$router.push({ name: "contact-qr-scan-full" });
} else {
this.$router.push({ name: "contact-qr" });
const qrNavigationService = QRNavigationService.getInstance();
const route = qrNavigationService.getQRScannerRoute();
this.$router.push(route);
}
/**
* Show export data prompt after adding a contact
* Prompts user to export their contact data as a backup
*/
private async showExportDataPrompt(): Promise<void> {
setTimeout(() => {
this.$notify(
{
group: "modal",
type: "confirm",
title: NOTIFY_EXPORT_DATA_PROMPT.title,
text: NOTIFY_EXPORT_DATA_PROMPT.message,
onYes: async () => {
await this.exportContactData();
},
yesText: "Export Data",
onNo: async () => {
// User chose not to export - no action needed
},
noText: "Not Now",
},
-1,
);
}, 1000); // Small delay to ensure success notification is shown first
}
/**
* Export contact data to JSON file
* Uses platform service to handle platform-specific export logic
*/
private async exportContactData(): Promise<void> {
try {
// Fetch all contacts from database
const allContacts = await this.$contacts();
// Convert contacts to export format
const exportData = contactsToExportJson(allContacts);
const jsonStr = JSON.stringify(exportData, null, 2);
// Generate filename with current date
const today = new Date();
const dateString = today.toISOString().split("T")[0]; // YYYY-MM-DD format
const fileName = `timesafari-backup-contacts-${dateString}.json`;
// Use platform service to handle export
await this.platformService.writeAndShareFile(fileName, jsonStr);
this.notify.success(
"Contact export completed successfully. Check your downloads or share dialog.",
);
} catch (error) {
logger.error("Export Error:", error);
this.notify.error(
`There was an error exporting the data: ${error instanceof Error ? error.message : "Unknown error"}`,
);
}
}
}