From 734e28667d5ddd438dc7e78d724554c5fdf3dba1 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Wed, 17 Apr 2024 20:07:09 -0600 Subject: [PATCH 1/4] add photo to profile page (not yet saved) --- src/db/tables/settings.ts | 7 ++- src/views/AccountViewView.vue | 111 +++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/src/db/tables/settings.ts b/src/db/tables/settings.ts index dbc4cf0..671b504 100644 --- a/src/db/tables/settings.ts +++ b/src/db/tables/settings.ts @@ -20,11 +20,12 @@ export type Settings = { filterFeedByNearby?: boolean; // filter by nearby filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden - firstName?: string; // User's first name + firstName?: string; // user's full name isRegistered?: boolean; lastName?: string; // deprecated - put all names in firstName - lastNotifiedClaimId?: string; // Last notified claim ID - lastViewedClaimId?: string; // Last viewed claim ID + lastNotifiedClaimId?: string; + lastViewedClaimId?: string; + profileImageUrl?: string; reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders reminderOn?: boolean; // Toggle to enable or disable reminders diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue index fb0b505..0875586 100644 --- a/src/views/AccountViewView.vue +++ b/src/views/AccountViewView.vue @@ -68,6 +68,30 @@ +
+ + + + + + + + + + +
(); @Component({ - components: { QuickNav, TopMessage }, + components: { GiftedPhotoDialog, QuickNav, TopMessage }, }) export default class AccountViewView extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; @@ -586,10 +611,12 @@ export default class AccountViewView extends Vue { isRegistered = false; isSubscribed = false; notificationMaybeChanged = false; + profileImageUrl: string | null = null; publicHex = ""; publicBase64 = ""; webPushServer = ""; webPushServerInput = ""; + limitsMessage = ""; loadingLimits = false; showContactGives = false; @@ -1260,5 +1287,87 @@ export default class AccountViewView extends Vue { -1, ); } + + openPhotoDialog() { + (this.$refs.photoDialog as GiftedPhotoDialog).open((imgUrl) => { + this.profileImageUrl = imgUrl; + console.log("Got image URL:", imgUrl); + }); + } + + confirmDeleteImage() { + this.$notify( + { + group: "modal", + type: "confirm", + title: "Are you sure you want to delete your profile picture?", + text: "", + onYes: this.deleteImage, + }, + -1, + ); + } + + async deleteImage() { + if (!this.profileImageUrl) { + return; + } + try { + const identity = await this.getIdentity(this.activeDid); + if (!identity) { + throw Error("No identity found."); + } + const token = await accessToken(identity); + const response = await this.axios.delete( + DEFAULT_IMAGE_API_SERVER + + "/image/" + + encodeURIComponent(this.profileImageUrl), + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + if (response.status === 204) { + // don't bother with a notification + // (either they'll simply continue or they're canceling and going back) + } else { + console.error("Non-success deleting image:", response); + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "There was a problem deleting the image.", + }, + 5000, + ); + // keep the imageUrl in localStorage so the user can try again if they want + return; + } + + this.profileImageUrl = ""; + } catch (error) { + console.error("Error deleting image:", error); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((error as any).response.status === 404) { + console.log("The image was already deleted:", error); + + this.profileImageUrl = ""; + + // it already doesn't exist so we won't say anything to the user + } else { + this.$notify( + { + group: "alert", + type: "danger", + title: "Error", + text: "There was an error deleting the image.", + }, + 5000, + ); + } + } + } } From b11cf81bf98943fe841b00c6e2990afe3714f138 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Thu, 18 Apr 2024 20:27:43 -0600 Subject: [PATCH 2/4] crop the image and store online and in settings --- package-lock.json | 45 ++++++++++++++++++++++ package.json | 3 +- src/components/GiftedPhotoDialog.vue | 56 ++++++++++++++++++++++------ src/views/AccountViewView.vue | 13 +++++-- 4 files changed, 101 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca08fc8..ee6df90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "vue": "^3.4.21", "vue-axios": "^3.5.2", "vue-facing-decorator": "^3.0.4", + "vue-picture-cropper": "^0.7.0", "vue-qrcode-reader": "^5.5.3", "vue-router": "^4.3.0", "web-did-resolver": "^2.0.27" @@ -2369,6 +2370,14 @@ "node": ">=6.9.0" } }, + "node_modules/@bassist/utils": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@bassist/utils/-/utils-0.4.0.tgz", + "integrity": "sha512-aoFTl0jUjm8/tDZodP41wnEkvB+C5O9NFCuYN/ztL6jSUSsuBkXq90/1ifBm1XhV/zySHgLYlU1+tgo3XtQ+nA==", + "dependencies": { + "@withtypes/mime": "^0.1.2" + } + }, "node_modules/@bitauth/libauth": { "version": "1.19.1", "license": "MIT", @@ -9696,6 +9705,25 @@ } } }, + "node_modules/@withtypes/mime": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@withtypes/mime/-/mime-0.1.2.tgz", + "integrity": "sha512-PB9BfZGzwblUONJY0LiOwsHCA6uV3DIPj/w9ReekdHxPOl0VdUFgI5s4avKycuuq9Gf5Nz2ZPA2O36GAUzlMPA==", + "dependencies": { + "mime": "^3.0.0" + } + }, + "node_modules/@withtypes/mime/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.7.13", "license": "MIT", @@ -11439,6 +11467,11 @@ "license": "SEE LICENSE IN LICENSE.md", "optional": true }, + "node_modules/cropperjs": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.1.tgz", + "integrity": "sha512-F4wsi+XkDHCOMrHMYjrTEE4QBOrsHHN5/2VsVAaRq8P7E5z7xQpT75S+f/9WikmBEailas3+yo+6zPIomW+NOA==" + }, "node_modules/cross-fetch": { "version": "4.0.0", "license": "MIT", @@ -21419,6 +21452,18 @@ "vue": "^3.0.0" } }, + "node_modules/vue-picture-cropper": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/vue-picture-cropper/-/vue-picture-cropper-0.7.0.tgz", + "integrity": "sha512-NF7+Dgso6d0GB16E5d/BbrcTIHm1VWz8dS3IjLhoBl+ZeC+yDA46CyJphQuO32SisaPmrKHN8VbiE2LgAfhnkQ==", + "dependencies": { + "@bassist/utils": "^0.4.0", + "cropperjs": "^1.6.1" + }, + "peerDependencies": { + "vue": ">=3.2.13" + } + }, "node_modules/vue-qrcode-reader": { "version": "5.5.3", "license": "MIT", diff --git a/package.json b/package.json index e0af68f..b5b5a6a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dev": "vite", "serve": "vite preview", "build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build", - "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src", + "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src", "lint-fix": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src", "prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js" }, @@ -62,6 +62,7 @@ "vue": "^3.4.21", "vue-axios": "^3.5.2", "vue-facing-decorator": "^3.0.4", + "vue-picture-cropper": "^0.7.0", "vue-qrcode-reader": "^5.5.3", "vue-router": "^4.3.0", "web-did-resolver": "^2.0.27" diff --git a/src/components/GiftedPhotoDialog.vue b/src/components/GiftedPhotoDialog.vue index 8fb17bf..6c388fd 100644 --- a/src/components/GiftedPhotoDialog.vue +++ b/src/components/GiftedPhotoDialog.vue @@ -20,28 +20,54 @@
- +
-
+
+ + +
+
+
+ +
+
+
+
+
-
- -
-

- {{ givenName }} - - - -
- - - - - - - - - - -
-

+
+

+ {{ givenName }} + + + +

+
+
+ + + + + + + + +
+
+
+ ... and those without your image see this: +
+
+ +
+
+
+
+ +
+
ID
@@ -580,6 +613,8 @@ import { ImageRateLimits, } from "@/libs/endorserServer"; import { Buffer } from "buffer/"; +import EntityIcon from "@/components/EntityIcon.vue"; +import {Contact} from "@/db/tables/contacts"; interface IAccount { did: string; @@ -591,7 +626,7 @@ interface IAccount { const inputFileNameRef = ref(); @Component({ - components: { GiftedPhotoDialog, QuickNav, TopMessage }, + components: {EntityIcon, GiftedPhotoDialog, QuickNav, TopMessage }, }) export default class AccountViewView extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; @@ -614,6 +649,8 @@ export default class AccountViewView extends Vue { profileImageUrl?: string; publicHex = ""; publicBase64 = ""; + showLargeIdenticonId?: string; + showLargeIdenticonUrl?: string; webPushServer = ""; webPushServerInput = ""; @@ -1290,14 +1327,18 @@ export default class AccountViewView extends Vue { } openPhotoDialog() { - (this.$refs.photoDialog as GiftedPhotoDialog).open(async (imgUrl) => { - await db.open(); - db.settings.update(MASTER_SETTINGS_KEY, { - profileImageUrl: imgUrl, - }); - this.profileImageUrl = imgUrl; - //console.log("Got image URL:", imgUrl); - }, true); + (this.$refs.photoDialog as GiftedPhotoDialog).open( + async (imgUrl) => { + await db.open(); + db.settings.update(MASTER_SETTINGS_KEY, { + profileImageUrl: imgUrl, + }); + this.profileImageUrl = imgUrl; + //console.log("Got image URL:", imgUrl); + }, + true, + "profile", + ); } confirmDeleteImage() { @@ -1351,14 +1392,14 @@ export default class AccountViewView extends Vue { return; } - this.profileImageUrl = ""; + this.profileImageUrl = undefined; } catch (error) { console.error("Error deleting image:", error); // eslint-disable-next-line @typescript-eslint/no-explicit-any if ((error as any).response.status === 404) { console.log("The image was already deleted:", error); - this.profileImageUrl = ""; + this.profileImageUrl = undefined; // it already doesn't exist so we won't say anything to the user } else { diff --git a/src/views/ClaimView.vue b/src/views/ClaimView.vue index f623716..e47e40e 100644 --- a/src/views/ClaimView.vue +++ b/src/views/ClaimView.vue @@ -415,12 +415,11 @@ import { accessToken } from "@/libs/crypto"; import * as serverUtil from "@/libs/endorserServer"; import * as libsUtil from "@/libs/util"; import QuickNav from "@/components/QuickNav.vue"; -import EntityIcon from "@/components/EntityIcon.vue"; import { Account } from "@/db/tables/accounts"; import { GiverInputInfo } from "@/libs/endorserServer"; @Component({ - components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav }, + components: { GiftedDialog, OfferDialog, QuickNav }, }) export default class ClaimView extends Vue { $notify!: (notification: NotificationIface, timeout?: number) => void; diff --git a/src/views/ContactGiftingView.vue b/src/views/ContactGiftingView.vue index d522729..446abcc 100644 --- a/src/views/ContactGiftingView.vue +++ b/src/views/ContactGiftingView.vue @@ -47,7 +47,7 @@

diff --git a/src/views/ContactQRScanShowView.vue b/src/views/ContactQRScanShowView.vue index 72cbc95..be3a0c7 100644 --- a/src/views/ContactQRScanShowView.vue +++ b/src/views/ContactQRScanShowView.vue @@ -44,7 +44,8 @@ :dotsOptions="{ type: 'square' }" class="flex justify-center" /> - Click QR to copy your contact URL to your clipboard. + Click that QR to copy your contact URL to your clipboard. +
Not scanning? Show it in pieces.

You have no identitifiers yet, so @@ -81,7 +82,7 @@ import { useClipboard } from "@vueuse/core"; import { NotificationIface } from "@/constants/app"; import { accountsDB, db } from "@/db/index"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; -import { deriveAddress, nextDerivationPath, SimpleSigner } from "@/libs/crypto"; +import {deriveAddress, getContactPayloadFromJwtUrl, nextDerivationPath, SimpleSigner} from "@/libs/crypto"; import QuickNav from "@/components/QuickNav.vue"; import { Account } from "@/db/tables/accounts"; import { @@ -153,6 +154,7 @@ export default class ContactQRScanShow extends Vue { (settings?.lastName ? ` ${settings.lastName}` : ""), // deprecated, pre v 0.1.3 publicEncKey, nextPublicEncKeyHash: nextPublicEncKeyHashBase64, + profileImageUrl: settings?.profileImageUrl, }, }; @@ -177,9 +179,24 @@ export default class ContactQRScanShow extends Vue { // Unfortunately, there are not typescript definitions for the qrcode-stream component yet. // eslint-disable-next-line @typescript-eslint/no-explicit-any onScanDetect(content: any) { - if (content[0]?.rawValue) { - localStorage.setItem("contactEndorserUrl", content[0].rawValue); - this.$router.push({ name: "contacts" }); + const url = content[0]?.rawValue; + if (url) { + try { + const fullData = getContactPayloadFromJwtUrl(url); + console.log("fullData", fullData); + localStorage.setItem("contactEndorserUrl", url); + this.$router.push({ name: "contacts" }); + } catch (e) { + this.$notify( + { + group: "alert", + type: "warning", + title: "Invalid Contact QR Code", + text: "The QR code isn't in the right format.", + }, + 5000, + ); + } } else { this.$notify( { @@ -188,7 +205,7 @@ export default class ContactQRScanShow extends Vue { title: "Invalid Contact QR Code", text: "No QR code detected with contact information.", }, - -1, + 5000, ); } } @@ -203,7 +220,7 @@ export default class ContactQRScanShow extends Vue { title: "Invalid Scan", text: "The scan was invalid.", }, - -1, + 5000, ); } diff --git a/src/views/ContactsView.vue b/src/views/ContactsView.vue index e5c699b..6f1119d 100644 --- a/src/views/ContactsView.vue +++ b/src/views/ContactsView.vue @@ -94,17 +94,17 @@

+ @click="showLargeIdenticon = contact" + /> {{ contact.name || AppString.NO_CONTACT_NAME }}

@@ -348,7 +348,7 @@ export default class ContactsView extends Vue { showGiveNumbers = false; showGiveTotals = true; showGiveConfirmed = true; - showLargeIdenticon = ""; + showLargeIdenticon?: Contact; AppString = AppString; libsUtil = libsUtil; @@ -672,6 +672,7 @@ export default class ContactsView extends Vue { did: payload.iss, name: payload.own.name, nextPubKeyHashB64: payload.own.nextPublicEncKeyHash, + profileImageUrl: payload.own.profileImageUrl, publicKeyBase64: payload.own.publicEncKey, } as Contact); } diff --git a/src/views/DiscoverView.vue b/src/views/DiscoverView.vue index 9126cc5..5256ca5 100644 --- a/src/views/DiscoverView.vue +++ b/src/views/DiscoverView.vue @@ -131,7 +131,6 @@ import { Component, Vue } from "vue-facing-decorator"; import QuickNav from "@/components/QuickNav.vue"; import InfiniteScroll from "@/components/InfiniteScroll.vue"; -import EntityIcon from "@/components/EntityIcon.vue"; import ProjectIcon from "@/components/ProjectIcon.vue"; import TopMessage from "@/components/TopMessage.vue"; import { NotificationIface } from "@/constants/app"; @@ -143,7 +142,6 @@ import { didInfo, PlanData } from "@/libs/endorserServer"; @Component({ components: { - EntityIcon, InfiniteScroll, ProjectIcon, QuickNav, diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 66511db..b9bc8cf 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -138,7 +138,7 @@ @click="openDialog(contact)" > diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index b460245..f5766d2 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -162,7 +162,7 @@ @click="openGiftDialog(contact)" > diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index 2639a5b..b4a7184 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -98,7 +98,7 @@ :entityId="offer.recipientDid" :iconSize="48" class="inline-block align-middle border border-slate-300 rounded-md" - > + />
From 606f21faecdd140b75d4b6c80d48fb79395ce131 Mon Sep 17 00:00:00 2001 From: Trent Larson Date: Fri, 19 Apr 2024 15:37:10 -0600 Subject: [PATCH 4/4] make the home screen elements load more quickly --- src/views/HomeView.vue | 120 +++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 64 deletions(-) diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index b9bc8cf..dc1479a 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -268,7 +268,6 @@