forked from trent_larson/crowd-funder-for-time-pwa
refactor: improve camera controls and modularize data export
- Add detailed error logging for image upload failures in PhotoDialog and SharedPhotoView - Extract DataExportSection into standalone component with proper prop handling - Fix Backup Identifier Seed visibility by passing activeDid prop
This commit is contained in:
114
src/components/DataExportSection.vue
Normal file
114
src/components/DataExportSection.vue
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
id="sectionDataExport"
|
||||||
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
||||||
|
>
|
||||||
|
<div class="mb-2 font-bold">Data Export</div>
|
||||||
|
<router-link
|
||||||
|
v-if="activeDid"
|
||||||
|
:to="{ name: 'seed-backup' }"
|
||||||
|
class="block w-full text-center text-md 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-1.5 py-2 rounded-md mb-2 mt-2"
|
||||||
|
>
|
||||||
|
Backup Identifier Seed
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<button
|
||||||
|
:class="computedStartDownloadLinkClassNames()"
|
||||||
|
class="block w-full text-center text-md 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-1.5 py-2 rounded-md"
|
||||||
|
@click="exportDatabase()"
|
||||||
|
>
|
||||||
|
Download Settings & Contacts
|
||||||
|
<br />
|
||||||
|
(excluding Identifier Data)
|
||||||
|
</button>
|
||||||
|
<a
|
||||||
|
ref="downloadLink"
|
||||||
|
:class="computedDownloadLinkClassNames()"
|
||||||
|
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.
|
||||||
|
</a>
|
||||||
|
<div class="mt-4">
|
||||||
|
<p>
|
||||||
|
After the download, you can save the file in your preferred storage
|
||||||
|
location.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li class="list-disc list-outside ml-4">
|
||||||
|
On iOS: Choose "More..." and select a place in iCloud, or go "Back"
|
||||||
|
and save to another location.
|
||||||
|
</li>
|
||||||
|
<li class="list-disc list-outside ml-4">
|
||||||
|
On Android: Choose "Open" and then share
|
||||||
|
<font-awesome icon="share-nodes" class="fa-fw" />
|
||||||
|
to your prefered place.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||||
|
import { NotificationIface } from "../constants/app";
|
||||||
|
import { db } from "../db/index";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class DataExportSection extends Vue {
|
||||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
@Prop({ required: true }) readonly activeDid!: string;
|
||||||
|
downloadUrl = "";
|
||||||
|
|
||||||
|
beforeUnmount() {
|
||||||
|
if (this.downloadUrl) {
|
||||||
|
URL.revokeObjectURL(this.downloadUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async exportDatabase() {
|
||||||
|
try {
|
||||||
|
const blob = await db.export({ prettyJson: true });
|
||||||
|
this.downloadUrl = URL.createObjectURL(blob);
|
||||||
|
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
|
||||||
|
downloadAnchor.href = this.downloadUrl;
|
||||||
|
downloadAnchor.download = `${db.name}-backup.json`;
|
||||||
|
downloadAnchor.click();
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Download Started",
|
||||||
|
text: "See your downloads directory for the backup. It is in the Dexie format.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Export Error:", error);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Export Error",
|
||||||
|
text: "There was an error exporting the data.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public computedStartDownloadLinkClassNames() {
|
||||||
|
return {
|
||||||
|
hidden: this.downloadUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public computedDownloadLinkClassNames() {
|
||||||
|
return {
|
||||||
|
hidden: !this.downloadUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -273,14 +273,14 @@ export default class PhotoDialog extends Vue {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log the raw error first
|
// Log the raw error first
|
||||||
logger.error("Raw error object:", JSON.stringify(error, null, 2));
|
logger.error("Raw error object:", JSON.stringify(error, null, 2));
|
||||||
|
|
||||||
let errorMessage = "There was an error saving the picture.";
|
let errorMessage = "There was an error saving the picture.";
|
||||||
|
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
const status = error.response?.status;
|
const status = error.response?.status;
|
||||||
const statusText = error.response?.statusText;
|
const statusText = error.response?.statusText;
|
||||||
const data = error.response?.data;
|
const data = error.response?.data;
|
||||||
|
|
||||||
// Log detailed error information
|
// Log detailed error information
|
||||||
logger.error("Upload error details:", {
|
logger.error("Upload error details:", {
|
||||||
status,
|
status,
|
||||||
@@ -290,16 +290,17 @@ export default class PhotoDialog extends Vue {
|
|||||||
config: {
|
config: {
|
||||||
url: error.config?.url,
|
url: error.config?.url,
|
||||||
method: error.config?.method,
|
method: error.config?.method,
|
||||||
headers: error.config?.headers
|
headers: error.config?.headers,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (status === 401) {
|
if (status === 401) {
|
||||||
errorMessage = "Authentication failed. Please try logging in again.";
|
errorMessage = "Authentication failed. Please try logging in again.";
|
||||||
} else if (status === 413) {
|
} else if (status === 413) {
|
||||||
errorMessage = "Image file is too large. Please try a smaller image.";
|
errorMessage = "Image file is too large. Please try a smaller image.";
|
||||||
} else if (status === 415) {
|
} else if (status === 415) {
|
||||||
errorMessage = "Unsupported image format. Please try a different image.";
|
errorMessage =
|
||||||
|
"Unsupported image format. Please try a different image.";
|
||||||
} else if (status && status >= 500) {
|
} else if (status && status >= 500) {
|
||||||
errorMessage = "Server error. Please try again later.";
|
errorMessage = "Server error. Please try again later.";
|
||||||
} else if (data?.message) {
|
} else if (data?.message) {
|
||||||
@@ -311,16 +312,16 @@ export default class PhotoDialog extends Vue {
|
|||||||
name: error.name,
|
name: error.name,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
error: JSON.stringify(error, Object.getOwnPropertyNames(error), 2)
|
error: JSON.stringify(error, Object.getOwnPropertyNames(error), 2),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Log any other type of error
|
// Log any other type of error
|
||||||
logger.error("Unknown error type:", {
|
logger.error("Unknown error type:", {
|
||||||
error: JSON.stringify(error, null, 2),
|
error: JSON.stringify(error, null, 2),
|
||||||
type: typeof error
|
type: typeof error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
|
|||||||
@@ -420,53 +420,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<DataExportSection :active-did="activeDid" />
|
||||||
id="sectionDataExport"
|
|
||||||
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
|
||||||
>
|
|
||||||
<div class="mb-2 font-bold">Data Export</div>
|
|
||||||
<router-link
|
|
||||||
v-if="activeDid"
|
|
||||||
:to="{ name: 'seed-backup' }"
|
|
||||||
class="block w-full text-center text-md 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-1.5 py-2 rounded-md mb-2 mt-2"
|
|
||||||
>
|
|
||||||
Backup Identifier Seed
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<button
|
|
||||||
:class="computedStartDownloadLinkClassNames()"
|
|
||||||
class="block w-full text-center text-md 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-1.5 py-2 rounded-md"
|
|
||||||
@click="exportDatabase()"
|
|
||||||
>
|
|
||||||
Download Settings & Contacts
|
|
||||||
<br />
|
|
||||||
(excluding Identifier Data)
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
ref="downloadLink"
|
|
||||||
:class="computedDownloadLinkClassNames()"
|
|
||||||
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.
|
|
||||||
</a>
|
|
||||||
<div class="mt-4">
|
|
||||||
<p>
|
|
||||||
After the download, you can save the file in your preferred storage
|
|
||||||
location.
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li class="list-disc list-outside ml-4">
|
|
||||||
On iOS: Choose "More..." and select a place in iCloud, or go "Back"
|
|
||||||
and save to another location.
|
|
||||||
</li>
|
|
||||||
<li class="list-disc list-outside ml-4">
|
|
||||||
On Android: Choose "Open" and then share
|
|
||||||
<font-awesome icon="share-nodes" class="fa-fw" />
|
|
||||||
to your prefered place.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- id used by puppeteer test script -->
|
<!-- id used by puppeteer test script -->
|
||||||
<h3
|
<h3
|
||||||
@@ -946,6 +900,7 @@ import PushNotificationPermission from "../components/PushNotificationPermission
|
|||||||
import QuickNav from "../components/QuickNav.vue";
|
import QuickNav from "../components/QuickNav.vue";
|
||||||
import TopMessage from "../components/TopMessage.vue";
|
import TopMessage from "../components/TopMessage.vue";
|
||||||
import UserNameDialog from "../components/UserNameDialog.vue";
|
import UserNameDialog from "../components/UserNameDialog.vue";
|
||||||
|
import DataExportSection from "../components/DataExportSection.vue";
|
||||||
import {
|
import {
|
||||||
AppString,
|
AppString,
|
||||||
DEFAULT_IMAGE_API_SERVER,
|
DEFAULT_IMAGE_API_SERVER,
|
||||||
@@ -999,6 +954,7 @@ const inputImportFileNameRef = ref<Blob>();
|
|||||||
QuickNav,
|
QuickNav,
|
||||||
TopMessage,
|
TopMessage,
|
||||||
UserNameDialog,
|
UserNameDialog,
|
||||||
|
DataExportSection,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
|
|||||||
@@ -223,14 +223,14 @@ export default class SharedPhotoView extends Vue {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log the raw error first
|
// Log the raw error first
|
||||||
logger.error("Raw error object:", JSON.stringify(error, null, 2));
|
logger.error("Raw error object:", JSON.stringify(error, null, 2));
|
||||||
|
|
||||||
let errorMessage = "There was an error saving the picture.";
|
let errorMessage = "There was an error saving the picture.";
|
||||||
|
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
const status = error.response?.status;
|
const status = error.response?.status;
|
||||||
const statusText = error.response?.statusText;
|
const statusText = error.response?.statusText;
|
||||||
const data = error.response?.data;
|
const data = error.response?.data;
|
||||||
|
|
||||||
// Log detailed error information
|
// Log detailed error information
|
||||||
logger.error("Upload error details:", {
|
logger.error("Upload error details:", {
|
||||||
status,
|
status,
|
||||||
@@ -240,16 +240,17 @@ export default class SharedPhotoView extends Vue {
|
|||||||
config: {
|
config: {
|
||||||
url: error.config?.url,
|
url: error.config?.url,
|
||||||
method: error.config?.method,
|
method: error.config?.method,
|
||||||
headers: error.config?.headers
|
headers: error.config?.headers,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (status === 401) {
|
if (status === 401) {
|
||||||
errorMessage = "Authentication failed. Please try logging in again.";
|
errorMessage = "Authentication failed. Please try logging in again.";
|
||||||
} else if (status === 413) {
|
} else if (status === 413) {
|
||||||
errorMessage = "Image file is too large. Please try a smaller image.";
|
errorMessage = "Image file is too large. Please try a smaller image.";
|
||||||
} else if (status === 415) {
|
} else if (status === 415) {
|
||||||
errorMessage = "Unsupported image format. Please try a different image.";
|
errorMessage =
|
||||||
|
"Unsupported image format. Please try a different image.";
|
||||||
} else if (status && status >= 500) {
|
} else if (status && status >= 500) {
|
||||||
errorMessage = "Server error. Please try again later.";
|
errorMessage = "Server error. Please try again later.";
|
||||||
} else if (data?.message) {
|
} else if (data?.message) {
|
||||||
@@ -261,16 +262,16 @@ export default class SharedPhotoView extends Vue {
|
|||||||
name: error.name,
|
name: error.name,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
error: JSON.stringify(error, Object.getOwnPropertyNames(error), 2)
|
error: JSON.stringify(error, Object.getOwnPropertyNames(error), 2),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Log any other type of error
|
// Log any other type of error
|
||||||
logger.error("Unknown error type:", {
|
logger.error("Unknown error type:", {
|
||||||
error: JSON.stringify(error, null, 2),
|
error: JSON.stringify(error, null, 2),
|
||||||
type: typeof error
|
type: typeof error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
|
|||||||
Reference in New Issue
Block a user