forked from trent_larson/crowd-funder-for-time-pwa
fix: Resolve contact export errors in DataExportSection
- Fix ref timing issue by always rendering download link element - Convert notify helper to getter to ensure $notify availability - Add proper error handling and resource cleanup for blob URLs - Improve user feedback with better error messages - Add comprehensive documentation and security considerations Resolves: TypeError on downloadLink.click() and notify function errors
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
/** * Data Export Section Component * * Provides UI and functionality for
|
/** * Data Export Section Component * * Provides UI and functionality for
|
||||||
exporting user data and backing up identifier seeds. * Includes buttons for seed
|
exporting user data and backing up identifier seeds. * Includes buttons for seed
|
||||||
backup and database export, with platform-specific download instructions. * *
|
backup and database export, with platform-specific download instructions. * *
|
||||||
@component * @displayName DataExportSection * @example * ```vue *
|
Features: * - Platform-specific export handling (web vs. native) * - Proper
|
||||||
|
resource cleanup for blob URLs * - Robust error handling with user-friendly
|
||||||
|
messages * - Conditional UI based on platform capabilities * * @component *
|
||||||
|
@displayName DataExportSection * @example * ```vue *
|
||||||
<DataExportSection :active-did="currentDid" />
|
<DataExportSection :active-did="currentDid" />
|
||||||
* ``` */
|
* ``` * * @author Matthew Raymer * @since 2025-01-25 * @version 1.1.0 */
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
@@ -26,11 +29,14 @@ backup and database export, with platform-specific download instructions. * *
|
|||||||
>
|
>
|
||||||
Download Contacts
|
Download Contacts
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Hidden download link for web platform - always rendered for ref access -->
|
||||||
<a
|
<a
|
||||||
v-if="isWebPlatform && downloadUrl"
|
v-if="isWebPlatform"
|
||||||
ref="downloadLink"
|
ref="downloadLink"
|
||||||
:href="downloadUrl"
|
:href="downloadUrl"
|
||||||
:download="fileName"
|
:download="fileName"
|
||||||
|
:class="{ hidden: !downloadUrl }"
|
||||||
class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
||||||
>
|
>
|
||||||
If no download happened yet, click again here to download now.
|
If no download happened yet, click again here to download now.
|
||||||
@@ -97,8 +103,11 @@ export default class DataExportSection extends Vue {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification helper for consistent notification patterns
|
* Notification helper for consistent notification patterns
|
||||||
|
* Created as a getter to ensure $notify is available when called
|
||||||
*/
|
*/
|
||||||
notify = createNotifyHelpers(this.$notify);
|
get notify() {
|
||||||
|
return createNotifyHelpers(this.$notify);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTE: PlatformServiceMixin provides both concise helpers (e.g. $contacts, capabilities)
|
* NOTE: PlatformServiceMixin provides both concise helpers (e.g. $contacts, capabilities)
|
||||||
@@ -135,6 +144,7 @@ export default class DataExportSection extends Vue {
|
|||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
if (this.downloadUrl && this.isWebPlatform) {
|
if (this.downloadUrl && this.isWebPlatform) {
|
||||||
URL.revokeObjectURL(this.downloadUrl);
|
URL.revokeObjectURL(this.downloadUrl);
|
||||||
|
this.downloadUrl = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,9 +193,31 @@ export default class DataExportSection extends Vue {
|
|||||||
*/
|
*/
|
||||||
private async handleWebExport(blob: Blob): Promise<void> {
|
private async handleWebExport(blob: Blob): Promise<void> {
|
||||||
this.downloadUrl = URL.createObjectURL(blob);
|
this.downloadUrl = URL.createObjectURL(blob);
|
||||||
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
|
|
||||||
downloadAnchor.click();
|
try {
|
||||||
setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000);
|
// Wait for next tick to ensure DOM is updated
|
||||||
|
await this.$nextTick();
|
||||||
|
|
||||||
|
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
|
||||||
|
if (!downloadAnchor) {
|
||||||
|
throw new Error("Download link element not found. Please try again.");
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadAnchor.click();
|
||||||
|
|
||||||
|
// Clean up the URL after a delay
|
||||||
|
setTimeout(() => {
|
||||||
|
URL.revokeObjectURL(this.downloadUrl);
|
||||||
|
this.downloadUrl = "";
|
||||||
|
}, 1000);
|
||||||
|
} catch (error) {
|
||||||
|
// Clean up the URL on error
|
||||||
|
if (this.downloadUrl) {
|
||||||
|
URL.revokeObjectURL(this.downloadUrl);
|
||||||
|
this.downloadUrl = "";
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user