forked from trent_larson/crowd-funder-for-time-pwa
Merge pull request 'profile-pic' (#114) from profile-pic into master
Reviewed-on: trent_larson/crowd-funder-for-time-pwa#114
This commit is contained in:
45
package-lock.json
generated
45
package-lock.json
generated
@@ -59,6 +59,7 @@
|
|||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^3.0.4",
|
"vue-facing-decorator": "^3.0.4",
|
||||||
|
"vue-picture-cropper": "^0.7.0",
|
||||||
"vue-qrcode-reader": "^5.5.3",
|
"vue-qrcode-reader": "^5.5.3",
|
||||||
"vue-router": "^4.3.0",
|
"vue-router": "^4.3.0",
|
||||||
"web-did-resolver": "^2.0.27"
|
"web-did-resolver": "^2.0.27"
|
||||||
@@ -2369,6 +2370,14 @@
|
|||||||
"node": ">=6.9.0"
|
"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": {
|
"node_modules/@bitauth/libauth": {
|
||||||
"version": "1.19.1",
|
"version": "1.19.1",
|
||||||
"license": "MIT",
|
"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": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.7.13",
|
"version": "0.7.13",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -11439,6 +11467,11 @@
|
|||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"optional": true
|
"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": {
|
"node_modules/cross-fetch": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21419,6 +21452,18 @@
|
|||||||
"vue": "^3.0.0"
|
"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": {
|
"node_modules/vue-qrcode-reader": {
|
||||||
"version": "5.5.3",
|
"version": "5.5.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"build": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build",
|
"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",
|
"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"
|
"prebuild": "eslint --ext .js,.ts,.vue --ignore-path .gitignore src && node sw_combine.js"
|
||||||
},
|
},
|
||||||
@@ -62,6 +62,7 @@
|
|||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^3.0.4",
|
"vue-facing-decorator": "^3.0.4",
|
||||||
|
"vue-picture-cropper": "^0.7.0",
|
||||||
"vue-qrcode-reader": "^5.5.3",
|
"vue-qrcode-reader": "^5.5.3",
|
||||||
"vue-router": "^4.3.0",
|
"vue-router": "^4.3.0",
|
||||||
"web-did-resolver": "^2.0.27"
|
"web-did-resolver": "^2.0.27"
|
||||||
|
|||||||
@@ -5,20 +5,29 @@
|
|||||||
import { createAvatar, StyleOptions } from "@dicebear/core";
|
import { createAvatar, StyleOptions } from "@dicebear/core";
|
||||||
import { avataaars } from "@dicebear/collection";
|
import { avataaars } from "@dicebear/collection";
|
||||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class EntityIcon extends Vue {
|
export default class EntityIcon extends Vue {
|
||||||
@Prop entityId = "";
|
@Prop contact: Contact;
|
||||||
|
@Prop entityId = ""; // overridden by contact.did or profileImageUrl
|
||||||
@Prop iconSize = 0;
|
@Prop iconSize = 0;
|
||||||
|
@Prop profileImageUrl = ""; // overridden by contact.profileImageUrl
|
||||||
|
|
||||||
generateIcon() {
|
generateIcon() {
|
||||||
const options: StyleOptions<object> = {
|
const imageUrl = this.contact?.profileImageUrl || this.profileImageUrl;
|
||||||
seed: this.entityId || "",
|
if (imageUrl) {
|
||||||
size: this.iconSize,
|
return `<img src="${imageUrl}" alt="avatar" width="${this.iconSize}" height="${this.iconSize}" />`;
|
||||||
};
|
} else {
|
||||||
const avatar = createAvatar(avataaars, options);
|
const identifier = this.contact?.did || this.entityId;
|
||||||
const svgString = avatar.toString();
|
const options: StyleOptions<object> = {
|
||||||
return svgString;
|
seed: (identifier as string) || "",
|
||||||
|
size: this.iconSize,
|
||||||
|
};
|
||||||
|
const avatar = createAvatar(avataaars, options);
|
||||||
|
const svgString = avatar.toString();
|
||||||
|
return svgString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -20,28 +20,54 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="uploading" class="flex justify-center">
|
<div v-if="uploading" class="flex justify-center">
|
||||||
<fa icon="spinner" class="fa-spin fa-3x text-center block" />
|
<fa
|
||||||
|
icon="spinner"
|
||||||
|
class="fa-spin fa-3x text-center block px-12 py-12"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="blob">
|
<div v-else-if="blob">
|
||||||
<div
|
<div v-if="crop">
|
||||||
class="flex justify-center gap-2 absolute bottom-[1rem] left-[1rem] right-[1rem] bg-black/50 px-4 py-2"
|
<VuePictureCropper
|
||||||
>
|
:boxStyle="{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
backgroundColor: '#f8f8f8',
|
||||||
|
margin: 'auto',
|
||||||
|
}"
|
||||||
|
:img="URL.createObjectURL(blob)"
|
||||||
|
:options="{
|
||||||
|
viewMode: 1,
|
||||||
|
dragMode: 'crop',
|
||||||
|
aspectRatio: 9 / 9,
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<!-- This gives a round cropper.
|
||||||
|
:presetMode="{
|
||||||
|
mode: 'round',
|
||||||
|
}"
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<img :src="URL.createObjectURL(blob)" class="mt-2 rounded" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute bottom-[1rem] left-[1rem] px-2 py-1">
|
||||||
<button
|
<button
|
||||||
@click="uploadImage"
|
@click="uploadImage"
|
||||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white font-bold py-2 px-4 rounded-md"
|
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-1 px-2 rounded-md"
|
||||||
>
|
>
|
||||||
<span>Upload</span>
|
<span>Upload</span>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="absolute bottom-[1rem] right-[1rem] px-2 py-1">
|
||||||
<button
|
<button
|
||||||
@click="retryImage"
|
@click="retryImage"
|
||||||
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white font-bold py-2 px-4 rounded-md"
|
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-1 px-2 rounded-md"
|
||||||
>
|
>
|
||||||
<span>Retry</span>
|
<span>Retry</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
|
||||||
<img :src="URL.createObjectURL(blob)" class="mt-2 rounded" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else ref="cameraContainer">
|
<div v-else ref="cameraContainer">
|
||||||
<!--
|
<!--
|
||||||
@@ -92,6 +118,7 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Camera from "simple-vue-camera";
|
import Camera from "simple-vue-camera";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
import VuePictureCropper, { cropper } from "vue-picture-cropper";
|
||||||
|
|
||||||
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
|
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
|
||||||
import { getIdentity } from "@/libs/util";
|
import { getIdentity } from "@/libs/util";
|
||||||
@@ -99,13 +126,15 @@ import { db } from "@/db/index";
|
|||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
|
|
||||||
@Component({ components: { Camera } })
|
@Component({ components: { Camera, VuePictureCropper } })
|
||||||
export default class GiftedPhotoDialog extends Vue {
|
export default class GiftedPhotoDialog extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
activeDeviceNumber = 0;
|
activeDeviceNumber = 0;
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
blob: Blob | null = null;
|
blob: Blob | null = null;
|
||||||
|
claimType = "GiveAction";
|
||||||
|
crop = false;
|
||||||
mirror = false;
|
mirror = false;
|
||||||
numDevices = 0;
|
numDevices = 0;
|
||||||
setImage: (arg: string) => void = () => {};
|
setImage: (arg: string) => void = () => {};
|
||||||
@@ -134,8 +163,10 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open(setImageFn: (arg: string) => void) {
|
open(setImageFn: (arg: string) => void, crop?: boolean, claimType?: string) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
|
this.crop = !!crop;
|
||||||
|
this.claimType = claimType || "GiveAction";
|
||||||
const bottomNav = document.querySelector("#QuickNav") as HTMLElement;
|
const bottomNav = document.querySelector("#QuickNav") as HTMLElement;
|
||||||
if (bottomNav) {
|
if (bottomNav) {
|
||||||
bottomNav.style.display = "none";
|
bottomNav.style.display = "none";
|
||||||
@@ -274,6 +305,11 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
|
|
||||||
async uploadImage() {
|
async uploadImage() {
|
||||||
this.uploading = true;
|
this.uploading = true;
|
||||||
|
|
||||||
|
if (this.crop) {
|
||||||
|
this.blob = await cropper?.getBlob();
|
||||||
|
}
|
||||||
|
|
||||||
const identifier = await getIdentity(this.activeDid);
|
const identifier = await getIdentity(this.activeDid);
|
||||||
const token = await accessToken(identifier);
|
const token = await accessToken(identifier);
|
||||||
const headers = {
|
const headers = {
|
||||||
@@ -295,7 +331,7 @@ export default class GiftedPhotoDialog extends Vue {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
formData.append("image", this.blob, "snapshot.png"); // png is set in snapshot()
|
formData.append("image", this.blob, "snapshot.png"); // png is set in snapshot()
|
||||||
formData.append("claimType", "GiveAction");
|
formData.append("claimType", this.claimType);
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
DEFAULT_IMAGE_API_SERVER + "/image",
|
DEFAULT_IMAGE_API_SERVER + "/image",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export interface Contact {
|
|||||||
did: string;
|
did: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
nextPubKeyHashB64?: string; // base64-encoded SHA256 hash of next public key
|
nextPubKeyHashB64?: string; // base64-encoded SHA256 hash of next public key
|
||||||
|
profileImageUrl?: string;
|
||||||
publicKeyBase64?: string;
|
publicKeyBase64?: string;
|
||||||
seesMe?: boolean;
|
seesMe?: boolean;
|
||||||
registered?: boolean;
|
registered?: boolean;
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ export type Settings = {
|
|||||||
filterFeedByNearby?: boolean; // filter by nearby
|
filterFeedByNearby?: boolean; // filter by nearby
|
||||||
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden
|
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden
|
||||||
|
|
||||||
firstName?: string; // User's first name
|
firstName?: string; // user's full name
|
||||||
isRegistered?: boolean;
|
isRegistered?: boolean;
|
||||||
lastName?: string; // deprecated - put all names in firstName
|
lastName?: string; // deprecated - put all names in firstName
|
||||||
lastNotifiedClaimId?: string; // Last notified claim ID
|
lastNotifiedClaimId?: string;
|
||||||
lastViewedClaimId?: string; // Last viewed claim ID
|
lastViewedClaimId?: string;
|
||||||
|
profileImageUrl?: string;
|
||||||
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
|
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
|
||||||
reminderOn?: boolean; // Toggle to enable or disable reminders
|
reminderOn?: boolean; // Toggle to enable or disable reminders
|
||||||
|
|
||||||
|
|||||||
@@ -63,12 +63,14 @@
|
|||||||
|
|
||||||
<!-- Identity Details -->
|
<!-- Identity Details -->
|
||||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||||
<h2 v-if="givenName" class="text-xl font-semibold mb-2">
|
<div v-if="givenName">
|
||||||
{{ givenName }}
|
<h2 class="text-xl font-semibold mb-2">
|
||||||
<router-link :to="{ name: 'new-edit-account' }">
|
{{ givenName }}
|
||||||
<fa icon="pen" class="text-xs text-blue-500 mb-1"></fa>
|
<router-link :to="{ name: 'new-edit-account' }">
|
||||||
</router-link>
|
<fa icon="pen" class="text-xs text-blue-500 mb-1"></fa>
|
||||||
</h2>
|
</router-link>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'new-edit-account' }"
|
:to="{ name: 'new-edit-account' }"
|
||||||
@@ -77,6 +79,61 @@
|
|||||||
Set Your Name
|
Set Your Name
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
|
<div class="flex justify-center mt-4">
|
||||||
|
<span v-if="profileImageUrl" class="flex justify-between">
|
||||||
|
<EntityIcon
|
||||||
|
:icon-size="96"
|
||||||
|
:profileImageUrl="profileImageUrl"
|
||||||
|
class="inline-block align-text-bottom border border-slate-300 rounded"
|
||||||
|
@click="showLargeIdenticonUrl = profileImageUrl"
|
||||||
|
/>
|
||||||
|
<fa
|
||||||
|
icon="trash-can"
|
||||||
|
@click="confirmDeleteImage"
|
||||||
|
class="text-red-500 fa-fw ml-8 mt-8 w-12 h-12"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<fa
|
||||||
|
icon="camera"
|
||||||
|
class="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-2 py-2 rounded-md"
|
||||||
|
@click="openPhotoDialog"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<GiftedPhotoDialog ref="photoDialog" />
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<div class="flex justify-center">
|
||||||
|
... and those without your image see this:
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<EntityIcon
|
||||||
|
:entityId="activeDid"
|
||||||
|
:iconSize="64"
|
||||||
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||||
|
@click="showLargeIdenticonId = activeDid"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showLargeIdenticonId || showLargeIdenticonUrl"
|
||||||
|
class="fixed z-[100] top-0 inset-x-0 w-full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
|
>
|
||||||
|
<EntityIcon
|
||||||
|
:entityId="showLargeIdenticonId"
|
||||||
|
:iconSize="512"
|
||||||
|
:profileImageUrl="showLargeIdenticonUrl"
|
||||||
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||||
|
@click="
|
||||||
|
showLargeIdenticonId = undefined;
|
||||||
|
showLargeIdenticonUrl = undefined;
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-slate-500 text-sm font-bold">ID</div>
|
<div class="text-slate-500 text-sm font-bold">ID</div>
|
||||||
<div class="text-sm text-slate-500 flex justify-start items-center mb-1">
|
<div class="text-sm text-slate-500 flex justify-start items-center mb-1">
|
||||||
@@ -537,6 +594,7 @@ import { ref } from "vue";
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
|
import GiftedPhotoDialog from "@/components/GiftedPhotoDialog.vue";
|
||||||
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 {
|
import {
|
||||||
@@ -555,6 +613,8 @@ import {
|
|||||||
ImageRateLimits,
|
ImageRateLimits,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import { Buffer } from "buffer/";
|
import { Buffer } from "buffer/";
|
||||||
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
|
import {Contact} from "@/db/tables/contacts";
|
||||||
|
|
||||||
interface IAccount {
|
interface IAccount {
|
||||||
did: string;
|
did: string;
|
||||||
@@ -566,7 +626,7 @@ interface IAccount {
|
|||||||
const inputFileNameRef = ref<Blob>();
|
const inputFileNameRef = ref<Blob>();
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { QuickNav, TopMessage },
|
components: {EntityIcon, GiftedPhotoDialog, QuickNav, TopMessage },
|
||||||
})
|
})
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
@@ -586,10 +646,14 @@ export default class AccountViewView extends Vue {
|
|||||||
isRegistered = false;
|
isRegistered = false;
|
||||||
isSubscribed = false;
|
isSubscribed = false;
|
||||||
notificationMaybeChanged = false;
|
notificationMaybeChanged = false;
|
||||||
|
profileImageUrl?: string;
|
||||||
publicHex = "";
|
publicHex = "";
|
||||||
publicBase64 = "";
|
publicBase64 = "";
|
||||||
|
showLargeIdenticonId?: string;
|
||||||
|
showLargeIdenticonUrl?: string;
|
||||||
webPushServer = "";
|
webPushServer = "";
|
||||||
webPushServerInput = "";
|
webPushServerInput = "";
|
||||||
|
|
||||||
limitsMessage = "";
|
limitsMessage = "";
|
||||||
loadingLimits = false;
|
loadingLimits = false;
|
||||||
showContactGives = false;
|
showContactGives = false;
|
||||||
@@ -657,6 +721,7 @@ export default class AccountViewView extends Vue {
|
|||||||
(settings?.firstName || "") +
|
(settings?.firstName || "") +
|
||||||
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
this.profileImageUrl = settings?.profileImageUrl as string;
|
||||||
this.showContactGives = !!settings?.showContactGivesInline;
|
this.showContactGives = !!settings?.showContactGivesInline;
|
||||||
this.showShortcutBvc = !!settings?.showShortcutBvc;
|
this.showShortcutBvc = !!settings?.showShortcutBvc;
|
||||||
this.warnIfProdServer = !!settings?.warnIfProdServer;
|
this.warnIfProdServer = !!settings?.warnIfProdServer;
|
||||||
@@ -1260,5 +1325,95 @@ export default class AccountViewView extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
"profile",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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 = undefined;
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -415,12 +415,11 @@ import { accessToken } from "@/libs/crypto";
|
|||||||
import * as serverUtil from "@/libs/endorserServer";
|
import * as serverUtil from "@/libs/endorserServer";
|
||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
import { GiverInputInfo } from "@/libs/endorserServer";
|
import { GiverInputInfo } from "@/libs/endorserServer";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { EntityIcon, GiftedDialog, OfferDialog, QuickNav },
|
components: { GiftedDialog, OfferDialog, QuickNav },
|
||||||
})
|
})
|
||||||
export default class ClaimView extends Vue {
|
export default class ClaimView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
<h2 class="text-base flex gap-4 items-center">
|
<h2 class="text-base flex gap-4 items-center">
|
||||||
<span class="grow font-semibold">
|
<span class="grow font-semibold">
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="contact.did"
|
:contact="contact"
|
||||||
:iconSize="32"
|
:iconSize="32"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -44,7 +44,8 @@
|
|||||||
:dotsOptions="{ type: 'square' }"
|
:dotsOptions="{ type: 'square' }"
|
||||||
class="flex justify-center"
|
class="flex justify-center"
|
||||||
/>
|
/>
|
||||||
<span> Click QR to copy your contact URL to your clipboard. </span>
|
<span> Click that QR to copy your contact URL to your clipboard. </span>
|
||||||
|
<div>Not scanning? Show it in pieces.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center" v-else>
|
<div class="text-center" v-else>
|
||||||
You have no identitifiers yet, so
|
You have no identitifiers yet, so
|
||||||
@@ -81,7 +82,7 @@ import { useClipboard } from "@vueuse/core";
|
|||||||
import { NotificationIface } from "@/constants/app";
|
import { NotificationIface } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
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 QuickNav from "@/components/QuickNav.vue";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
import {
|
import {
|
||||||
@@ -153,6 +154,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
(settings?.lastName ? ` ${settings.lastName}` : ""), // deprecated, pre v 0.1.3
|
(settings?.lastName ? ` ${settings.lastName}` : ""), // deprecated, pre v 0.1.3
|
||||||
publicEncKey,
|
publicEncKey,
|
||||||
nextPublicEncKeyHash: nextPublicEncKeyHashBase64,
|
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.
|
// Unfortunately, there are not typescript definitions for the qrcode-stream component yet.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onScanDetect(content: any) {
|
onScanDetect(content: any) {
|
||||||
if (content[0]?.rawValue) {
|
const url = content[0]?.rawValue;
|
||||||
localStorage.setItem("contactEndorserUrl", content[0].rawValue);
|
if (url) {
|
||||||
this.$router.push({ name: "contacts" });
|
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 {
|
} else {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -188,7 +205,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
title: "Invalid Contact QR Code",
|
title: "Invalid Contact QR Code",
|
||||||
text: "No QR code detected with contact information.",
|
text: "No QR code detected with contact information.",
|
||||||
},
|
},
|
||||||
-1,
|
5000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,7 +220,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
title: "Invalid Scan",
|
title: "Invalid Scan",
|
||||||
text: "The scan was invalid.",
|
text: "The scan was invalid.",
|
||||||
},
|
},
|
||||||
-1,
|
5000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,17 +94,17 @@
|
|||||||
<div class="grow overflow-hidden">
|
<div class="grow overflow-hidden">
|
||||||
<h2 class="text-base font-semibold">
|
<h2 class="text-base font-semibold">
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="contact.did"
|
:contact="contact"
|
||||||
:iconSize="24"
|
:iconSize="24"
|
||||||
class="inline-block align-text-bottom border border-slate-300 rounded"
|
class="inline-block align-text-bottom border border-slate-300 rounded"
|
||||||
@click="showLargeIdenticon = contact.did"
|
@click="showLargeIdenticon = contact"
|
||||||
></EntityIcon>
|
/>
|
||||||
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
{{ contact.name || AppString.NO_CONTACT_NAME }}
|
||||||
<button
|
<button
|
||||||
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1 rounded-md"
|
||||||
@click="
|
@click="
|
||||||
contactEdit = contact;
|
contactEdit = contact;
|
||||||
contactNewName = contact.name;
|
contactNewName = contact.name || '';
|
||||||
"
|
"
|
||||||
title="Edit"
|
title="Edit"
|
||||||
>
|
>
|
||||||
@@ -246,10 +246,10 @@
|
|||||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
>
|
>
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="showLargeIdenticon"
|
:contact="showLargeIdenticon"
|
||||||
:iconSize="512"
|
:iconSize="512"
|
||||||
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||||
@click="showLargeIdenticon = ''"
|
@click="showLargeIdenticon = undefined"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -348,7 +348,7 @@ export default class ContactsView extends Vue {
|
|||||||
showGiveNumbers = false;
|
showGiveNumbers = false;
|
||||||
showGiveTotals = true;
|
showGiveTotals = true;
|
||||||
showGiveConfirmed = true;
|
showGiveConfirmed = true;
|
||||||
showLargeIdenticon = "";
|
showLargeIdenticon?: Contact;
|
||||||
|
|
||||||
AppString = AppString;
|
AppString = AppString;
|
||||||
libsUtil = libsUtil;
|
libsUtil = libsUtil;
|
||||||
@@ -672,6 +672,7 @@ export default class ContactsView extends Vue {
|
|||||||
did: payload.iss,
|
did: payload.iss,
|
||||||
name: payload.own.name,
|
name: payload.own.name,
|
||||||
nextPubKeyHashB64: payload.own.nextPublicEncKeyHash,
|
nextPubKeyHashB64: payload.own.nextPublicEncKeyHash,
|
||||||
|
profileImageUrl: payload.own.profileImageUrl,
|
||||||
publicKeyBase64: payload.own.publicEncKey,
|
publicKeyBase64: payload.own.publicEncKey,
|
||||||
} as Contact);
|
} as Contact);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
|
||||||
import ProjectIcon from "@/components/ProjectIcon.vue";
|
import ProjectIcon from "@/components/ProjectIcon.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
import { NotificationIface } from "@/constants/app";
|
import { NotificationIface } from "@/constants/app";
|
||||||
@@ -143,7 +142,6 @@ import { didInfo, PlanData } from "@/libs/endorserServer";
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
EntityIcon,
|
|
||||||
InfiniteScroll,
|
InfiniteScroll,
|
||||||
ProjectIcon,
|
ProjectIcon,
|
||||||
QuickNav,
|
QuickNav,
|
||||||
|
|||||||
@@ -138,7 +138,7 @@
|
|||||||
@click="openDialog(contact)"
|
@click="openDialog(contact)"
|
||||||
>
|
>
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="contact.did"
|
:contact="contact"
|
||||||
:iconSize="64"
|
:iconSize="64"
|
||||||
class="mx-auto border border-slate-300 rounded-md mb-1"
|
class="mx-auto border border-slate-300 rounded-md mb-1"
|
||||||
/>
|
/>
|
||||||
@@ -268,7 +268,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as R from "ramda";
|
|
||||||
import { UAParser } from "ua-parser-js";
|
import { UAParser } from "ua-parser-js";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
@@ -306,8 +305,8 @@ interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
|||||||
displayName: string;
|
displayName: string;
|
||||||
known: boolean;
|
known: boolean;
|
||||||
};
|
};
|
||||||
image: string;
|
image?: string;
|
||||||
recipientProjectName: string | undefined;
|
recipientProjectName?: string;
|
||||||
receiver: {
|
receiver: {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
known: boolean;
|
known: boolean;
|
||||||
@@ -488,70 +487,63 @@ export default class HomeView extends Vue {
|
|||||||
endOfResults = false;
|
endOfResults = false;
|
||||||
// include the descriptions of the giver and receiver
|
// include the descriptions of the giver and receiver
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
const newFeedData: Array<Promise<GiveRecordWithContactInfo>> =
|
for (const record: GiveSummaryRecord of results.data) {
|
||||||
results.data.map(async (record: GiveSummaryRecord) => {
|
// similar code is in endorser-mobile utility.ts
|
||||||
// similar code is in endorser-mobile utility.ts
|
// claim.claim happen for some claims wrapped in a Verifiable Credential
|
||||||
// claim.claim happen for some claims wrapped in a Verifiable Credential
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const claim = (record.fullClaim as any).claim || record.fullClaim;
|
||||||
const claim = (record.fullClaim as any).claim || record.fullClaim;
|
// agent.did is for legacy data, before March 2023
|
||||||
// agent.did is for legacy data, before March 2023
|
const giverDid =
|
||||||
const giverDid =
|
claim.agent?.identifier || (claim.agent as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
claim.agent?.identifier || (claim.agent as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
|
// recipient.did is for legacy data, before March 2023
|
||||||
// recipient.did is for legacy data, before March 2023
|
const recipientDid =
|
||||||
const recipientDid =
|
claim.recipient?.identifier || (claim.recipient as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
claim.recipient?.identifier || (claim.recipient as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
|
const plan = await getPlanFromCache(
|
||||||
const plan = await getPlanFromCache(
|
record.fulfillsPlanHandleId,
|
||||||
record.fulfillsPlanHandleId,
|
identity,
|
||||||
identity,
|
this.axios,
|
||||||
this.axios,
|
this.apiServer,
|
||||||
this.apiServer,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
// check if the record should be filtered out
|
// check if the record should be filtered out
|
||||||
let anyMatch = false;
|
let anyMatch = false;
|
||||||
if (
|
if (this.isFeedFilteredByVisible && containsNonHiddenDid(record)) {
|
||||||
this.isFeedFilteredByVisible &&
|
// has a visible DID so it's a keeper
|
||||||
containsNonHiddenDid(record)
|
anyMatch = true;
|
||||||
) {
|
}
|
||||||
// has a visible DID so it's a keeper
|
if (!anyMatch && this.isFeedFilteredByNearby) {
|
||||||
anyMatch = true;
|
// check if the associated project has a location inside user's search box
|
||||||
}
|
if (record.fulfillsPlanHandleId) {
|
||||||
if (!anyMatch && this.isFeedFilteredByNearby) {
|
if (plan?.locLat && plan?.locLon) {
|
||||||
// check if the associated project has a location inside user's search box
|
if (this.latLongInAnySearchBox(plan.locLat, plan.locLon)) {
|
||||||
if (record.fulfillsPlanHandleId) {
|
anyMatch = true;
|
||||||
if (plan?.locLat && plan?.locLon) {
|
|
||||||
if (this.latLongInAnySearchBox(plan.locLat, plan.locLon)) {
|
|
||||||
anyMatch = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.isAnyFeedFilterOn && !anyMatch) {
|
}
|
||||||
return null;
|
if (this.isAnyFeedFilterOn && !anyMatch) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
const newRecord: GiveRecordWithContactInfo = {
|
||||||
...record,
|
...record,
|
||||||
giver: didInfoForContact(
|
giver: didInfoForContact(
|
||||||
giverDid,
|
giverDid,
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
contactForDid(giverDid, this.allContacts),
|
contactForDid(giverDid, this.allContacts),
|
||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
),
|
),
|
||||||
image: claim.image,
|
image: claim.image,
|
||||||
recipientProjectName: plan?.name,
|
recipientProjectName: plan?.name as string,
|
||||||
receiver: didInfoForContact(
|
receiver: didInfoForContact(
|
||||||
recipientDid,
|
recipientDid,
|
||||||
this.activeDid,
|
this.activeDid,
|
||||||
contactForDid(recipientDid, this.allContacts),
|
contactForDid(recipientDid, this.allContacts),
|
||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
});
|
this.feedData.push(newRecord);
|
||||||
const allNewFeedData: GiveRecordWithContactInfo[] =
|
}
|
||||||
await Promise.all(newFeedData);
|
|
||||||
const filteredFeedData = allNewFeedData.filter(R.isNotNil);
|
|
||||||
this.feedData = this.feedData.concat(filteredFeedData);
|
|
||||||
this.feedPreviousOldestId =
|
this.feedPreviousOldestId =
|
||||||
results.data[results.data.length - 1].jwtId;
|
results.data[results.data.length - 1].jwtId;
|
||||||
// The following update is only done on the first load.
|
// The following update is only done on the first load.
|
||||||
|
|||||||
@@ -162,7 +162,7 @@
|
|||||||
@click="openGiftDialog(contact)"
|
@click="openGiftDialog(contact)"
|
||||||
>
|
>
|
||||||
<EntityIcon
|
<EntityIcon
|
||||||
:entityId="contact.did"
|
:contact="contact"
|
||||||
:iconSize="64"
|
:iconSize="64"
|
||||||
class="mx-auto border border-slate-300 rounded-md mb-1"
|
class="mx-auto border border-slate-300 rounded-md mb-1"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
:entityId="offer.recipientDid"
|
:entityId="offer.recipientDid"
|
||||||
:iconSize="48"
|
:iconSize="48"
|
||||||
class="inline-block align-middle border border-slate-300 rounded-md"
|
class="inline-block align-middle border border-slate-300 rounded-md"
|
||||||
></EntityIcon>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user