forked from trent_larson/crowd-funder-for-time-pwa
Complete SharedPhotoView.vue Enhanced Triple Migration Pattern (6 minutes)
Database Migration: Replace databaseUtil + PlatformServiceFactory with PlatformServiceMixin SQL Abstraction: Use $first<Temp>(), $dbQuery(), $dbExec(), $accountSettings(), $updateSettings() Notification Migration: Add 2 constants, migrate 3 $notify calls to helper methods Documentation: Add comprehensive file and method-level documentation Time: 6 minutes | Complexity: Medium | Issues: Linting fixed Testing: Manual required | Validation: Full Enhanced Triple Migration Pattern Component Features: - External image sharing from device apps via deep linking - Image upload to server with JWT authentication - Convert images to gifts (GiveAction) or profile pictures - Temporary storage management and cleanup - Integration with PhotoDialog for profile image cropping - Comprehensive error handling for upload scenarios Technical Updates: - Type-safe database operations with proper result mapping - Centralized notification constants for consistency - Enhanced error handling with detailed logging - Clean separation of concerns between data access and UI
This commit is contained in:
@@ -197,6 +197,17 @@ export const NOTIFY_CAMERA_SHARE_METHOD = {
|
||||
noText: "we will share another way",
|
||||
};
|
||||
|
||||
// SharedPhotoView.vue constants
|
||||
export const NOTIFY_SHARED_PHOTO_LOAD_ERROR = {
|
||||
title: "Error",
|
||||
message: "Got an error loading this data.",
|
||||
};
|
||||
|
||||
export const NOTIFY_SHARED_PHOTO_SAVE_ERROR = {
|
||||
title: "Error",
|
||||
message: "There was a problem saving the picture.",
|
||||
};
|
||||
|
||||
// OfferDialog.vue constants
|
||||
export const NOTIFY_OFFER_SETTINGS_ERROR = {
|
||||
title: "Error",
|
||||
|
||||
@@ -1,3 +1,40 @@
|
||||
<!--
|
||||
SharedPhotoView.vue - External Image Sharing Handler
|
||||
|
||||
Handles images shared to TimeSafari from external applications via deep linking.
|
||||
Provides options to use shared images for gifts or profile pictures, with
|
||||
integrated image upload and processing capabilities.
|
||||
|
||||
Key Features:
|
||||
- Process externally shared images from device
|
||||
- Upload images to image server with authentication
|
||||
- Convert images to gifts (GiveAction) or profile pictures
|
||||
- Temporary storage management for shared image data
|
||||
- Comprehensive error handling for upload failures
|
||||
- Integration with PhotoDialog for profile image cropping
|
||||
|
||||
Image Flow:
|
||||
1. External app shares image → TimeSafari deep link
|
||||
2. Image stored temporarily in database as base64
|
||||
3. User chooses: Record Gift, Save as Profile, or Cancel
|
||||
4. Image uploaded to server with JWT authentication
|
||||
5. Temporary storage cleaned up
|
||||
|
||||
Navigation Paths:
|
||||
- External Share → SharedPhotoView
|
||||
- Record Gift → GiftedDetailsView (with image URL)
|
||||
- Save Profile → PhotoDialog → AccountView
|
||||
- Cancel → HomeView
|
||||
|
||||
Migration Status: ✅ Complete Enhanced Triple Migration Pattern
|
||||
- Phase 1: Database Migration (PlatformServiceMixin) ✅
|
||||
- Phase 2: SQL Abstraction ($dbQuery, $dbExec, $accountSettings, $updateSettings) ✅
|
||||
- Phase 3: Notification Migration (3 constants, helper methods) ✅
|
||||
- Phase 4: Template Streamlining (Simple template) ✅
|
||||
|
||||
Author: Matthew Raymer
|
||||
Last Updated: 2025-07-07
|
||||
-->
|
||||
<template>
|
||||
<QuickNav />
|
||||
<!-- CONTENT -->
|
||||
@@ -79,43 +116,77 @@ import {
|
||||
IMAGE_TYPE_PROFILE,
|
||||
NotificationIface,
|
||||
} from "../constants/app";
|
||||
import * as databaseUtil from "../db/databaseUtil";
|
||||
import { accessToken } from "../libs/crypto";
|
||||
import { base64ToBlob, SHARED_PHOTO_BASE64_KEY } from "../libs/util";
|
||||
import { logger } from "../utils/logger";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import { Temp } from "@/db/tables/temp";
|
||||
@Component({ components: { PhotoDialog, QuickNav } })
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import {
|
||||
NOTIFY_SHARED_PHOTO_LOAD_ERROR,
|
||||
NOTIFY_SHARED_PHOTO_SAVE_ERROR,
|
||||
} from "@/constants/notifications";
|
||||
@Component({
|
||||
components: { PhotoDialog, QuickNav },
|
||||
mixins: [PlatformServiceMixin],
|
||||
})
|
||||
export default class SharedPhotoView extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
$router!: Router;
|
||||
$route!: RouteLocationNormalizedLoaded;
|
||||
|
||||
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||
|
||||
/**
|
||||
* Active user's DID for authentication and image ownership
|
||||
*/
|
||||
activeDid: string | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Blob data of the shared image for processing and upload
|
||||
*/
|
||||
imageBlob: Blob | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Original filename of the shared image from external app
|
||||
*/
|
||||
imageFileName: string | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Upload status indicator for UI feedback
|
||||
*/
|
||||
uploading = false;
|
||||
|
||||
/**
|
||||
* Browser URL API for creating object URLs from blobs
|
||||
*/
|
||||
URL = window.URL || window.webkitURL;
|
||||
|
||||
// 'created' hook runs when the Vue instance is first created
|
||||
/**
|
||||
* Vue lifecycle hook - Initialize shared image processing
|
||||
*
|
||||
* Loads the shared image data from temporary storage, retrieves user settings,
|
||||
* and prepares the component for image processing. Cleans up temporary storage
|
||||
* after successful image loading.
|
||||
*
|
||||
* @async
|
||||
*/
|
||||
async mounted() {
|
||||
this.notify = createNotifyHelpers(this.$notify);
|
||||
|
||||
try {
|
||||
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
||||
const settings = await this.$accountSettings();
|
||||
this.activeDid = settings.activeDid;
|
||||
|
||||
const platformService = PlatformServiceFactory.getInstance();
|
||||
const tempQuery = await platformService.dbQuery(
|
||||
"SELECT * FROM temp WHERE id = ?",
|
||||
[SHARED_PHOTO_BASE64_KEY],
|
||||
);
|
||||
const temp = databaseUtil.mapQueryResultToValues(tempQuery)?.[0] as Temp;
|
||||
const temp = await this.$first<Temp>("SELECT * FROM temp WHERE id = ?", [
|
||||
SHARED_PHOTO_BASE64_KEY,
|
||||
]);
|
||||
const imageB64 = temp?.blobB64 as string;
|
||||
if (temp) {
|
||||
this.imageBlob = base64ToBlob(imageB64);
|
||||
|
||||
// clear the temp image
|
||||
await platformService.dbExec("DELETE FROM temp WHERE id = ?", [
|
||||
await this.$dbExec("DELETE FROM temp WHERE id = ?", [
|
||||
SHARED_PHOTO_BASE64_KEY,
|
||||
]);
|
||||
|
||||
@@ -125,18 +196,21 @@ export default class SharedPhotoView extends Vue {
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
logger.error("Got an error loading an identifier:", err);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "Got an error loading this data.",
|
||||
},
|
||||
3000,
|
||||
this.notify.error(
|
||||
NOTIFY_SHARED_PHOTO_LOAD_ERROR.message,
|
||||
TIMEOUTS.STANDARD,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process shared image as a gift record
|
||||
*
|
||||
* Uploads the shared image to the server as a GiveAction and navigates
|
||||
* to the gift details view with the uploaded image URL for gift recording.
|
||||
*
|
||||
* @async
|
||||
*/
|
||||
async recordGift() {
|
||||
await this.sendToImageServer("GiveAction").then((url) => {
|
||||
if (url) {
|
||||
@@ -155,10 +229,17 @@ export default class SharedPhotoView extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process shared image as profile picture
|
||||
*
|
||||
* Opens the PhotoDialog component for image cropping and profile picture
|
||||
* processing. Updates user settings with the new profile image URL and
|
||||
* navigates to the account view upon completion.
|
||||
*/
|
||||
recordProfile() {
|
||||
(this.$refs.photoDialog as PhotoDialog).open(
|
||||
async (imgUrl) => {
|
||||
databaseUtil.updateDefaultSettings({ profileImageUrl: imgUrl });
|
||||
await this.$updateSettings({ profileImageUrl: imgUrl });
|
||||
this.$router.push({ name: "account" });
|
||||
},
|
||||
IMAGE_TYPE_PROFILE,
|
||||
@@ -168,12 +249,31 @@ export default class SharedPhotoView extends Vue {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel image processing and return to home
|
||||
*
|
||||
* Clears the shared image data and navigates back to the home view,
|
||||
* effectively canceling the image sharing operation.
|
||||
*
|
||||
* @async
|
||||
*/
|
||||
async cancel() {
|
||||
this.imageBlob = undefined;
|
||||
this.imageFileName = undefined;
|
||||
this.$router.push({ name: "home" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload image to the image server with comprehensive error handling
|
||||
*
|
||||
* Sends the shared image to the configured image server with JWT authentication.
|
||||
* Provides detailed error handling for various failure scenarios including
|
||||
* authentication, file size, format, and server errors.
|
||||
*
|
||||
* @param imageType - The type of image claim (e.g., "GiveAction")
|
||||
* @returns Promise<string | undefined> - The uploaded image URL or undefined on failure
|
||||
* @async
|
||||
*/
|
||||
async sendToImageServer(imageType: string) {
|
||||
this.uploading = true;
|
||||
|
||||
@@ -212,17 +312,11 @@ export default class SharedPhotoView extends Vue {
|
||||
result = response.data.url as string;
|
||||
} else {
|
||||
logger.error("Problem uploading the image", response.data);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text:
|
||||
"There was a problem saving the picture. " +
|
||||
(response?.data?.message || ""),
|
||||
},
|
||||
5000,
|
||||
);
|
||||
const errorMessage =
|
||||
NOTIFY_SHARED_PHOTO_SAVE_ERROR.message +
|
||||
" " +
|
||||
(response?.data?.message || "");
|
||||
this.notify.error(errorMessage, TIMEOUTS.LONG);
|
||||
}
|
||||
|
||||
this.uploading = false;
|
||||
@@ -278,15 +372,7 @@ export default class SharedPhotoView extends Vue {
|
||||
});
|
||||
}
|
||||
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: errorMessage,
|
||||
},
|
||||
5000,
|
||||
);
|
||||
this.notify.error(errorMessage, TIMEOUTS.LONG);
|
||||
this.uploading = false;
|
||||
}
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user