Compare commits
4 Commits
electron_f
...
eye-slash
| Author | SHA1 | Date | |
|---|---|---|---|
| 5275e00b67 | |||
| 071792b97c | |||
| bf2f23021f | |||
| 829870b16c |
@@ -12,15 +12,18 @@
|
||||
|
||||
<div class="bg-slate-100 rounded-t-md border border-slate-300 p-3 sm:p-4">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<img
|
||||
src="https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg"
|
||||
class="size-8 object-cover rounded-full"
|
||||
|
||||
<EntityIcon
|
||||
:entity-id="record.issuerDid"
|
||||
:profile-image-url="record.issuer.profileImageUrl"
|
||||
:icon-size="24"
|
||||
class="rounded object-cover"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<h3 class="font-semibold">
|
||||
{{
|
||||
record.giver.known ? record.giver.displayName : "Anonymous Giver"
|
||||
record.issuer.known ? record.issuer.displayName : ""
|
||||
}}
|
||||
</h3>
|
||||
<p class="ms-auto text-xs text-slate-500 italic">
|
||||
@@ -48,54 +51,40 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Giver - - > Receiver -->
|
||||
<div class="relative flex justify-between gap-4 max-w-lg mx-auto mb-5">
|
||||
<!-- Source -->
|
||||
<!-- Giver -->
|
||||
<div
|
||||
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
||||
>
|
||||
<div class="relative w-fit mx-auto">
|
||||
<template v-if="record.giver.profileImageUrl">
|
||||
<EntityIcon
|
||||
:profile-image-url="record.giver.profileImageUrl"
|
||||
:class="[
|
||||
!record.providerPlanName
|
||||
? 'rounded-full size-[3rem] sm:size-[4rem] object-cover'
|
||||
: 'rounded size-[3rem] sm:size-[4rem] object-cover',
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<!-- Project Icon -->
|
||||
<template v-if="record.providerPlanName">
|
||||
<div v-if="record.providerPlanName">
|
||||
<ProjectIcon
|
||||
:entity-id="record.providerPlanName"
|
||||
:icon-size="48"
|
||||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
||||
class="rounded *:w-full *:h-full"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Identicon for DIDs -->
|
||||
<template v-else-if="record.giver.did">
|
||||
<img
|
||||
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`"
|
||||
class="rounded-full size-[3rem] sm:size-[4rem]"
|
||||
alt="Identicon"
|
||||
/>
|
||||
</template>
|
||||
<!-- Unknown Person -->
|
||||
<template v-else>
|
||||
<fa
|
||||
icon="person-circle-question"
|
||||
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<EntityIcon
|
||||
v-else
|
||||
:entity-id="record.agentDid"
|
||||
:profile-image-url="record.issuer.profileImageUrl"
|
||||
:icon-size="48"
|
||||
class="rounded size-[3rem] sm:size-[4rem]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
|
||||
<fa
|
||||
:icon="record.providerPlanName ? 'building' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.giver.displayName }}
|
||||
<div v-if="record.providerPlanName || record.giver.known">
|
||||
<font-awesome
|
||||
:icon="record.providerPlanName ? 'users' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.providerPlanName || record.giver.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -118,53 +107,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Destination -->
|
||||
<!-- Recipient -->
|
||||
<div
|
||||
class="w-28 sm:w-40 text-center bg-white border border-slate-200 rounded p-2 sm:p-3"
|
||||
>
|
||||
<div class="relative w-fit mx-auto">
|
||||
<template v-if="record.receiver.profileImageUrl">
|
||||
<EntityIcon
|
||||
:profile-image-url="record.receiver.profileImageUrl"
|
||||
:class="[
|
||||
!record.recipientProjectName
|
||||
? 'rounded-full size-[3rem] sm:size-[4rem] object-cover'
|
||||
: 'rounded size-[3rem] sm:size-[4rem] object-cover',
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<!-- Project Icon -->
|
||||
<template v-if="record.recipientProjectName">
|
||||
<div v-if="record.recipientProjectName">
|
||||
<ProjectIcon
|
||||
:entity-id="record.recipientProjectName"
|
||||
:icon-size="48"
|
||||
class="rounded size-[3rem] sm:size-[4rem] *:w-full *:h-full"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Identicon for DIDs -->
|
||||
<template v-else-if="record.receiver.did">
|
||||
<img
|
||||
:src="`https://a.fsdn.com/con/app/proj/identicons/screenshots/225506.jpg`"
|
||||
class="rounded-full size-[3rem] sm:size-[4rem]"
|
||||
alt="Identicon"
|
||||
/>
|
||||
</template>
|
||||
<!-- Unknown Person -->
|
||||
<template v-else>
|
||||
<fa
|
||||
icon="person-circle-question"
|
||||
class="text-slate-300 text-[3rem] sm:text-[4rem]"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<EntityIcon
|
||||
v-else
|
||||
:entity-id="record.recipientDid"
|
||||
:profile-image-url="record.receiver.profileImageUrl"
|
||||
:icon-size="48"
|
||||
class="rounded size-[3rem] sm:size-[4rem]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs mt-2 line-clamp-3 sm:line-clamp-2">
|
||||
<fa
|
||||
:icon="record.recipientProjectName ? 'building' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.receiver.displayName }}
|
||||
<div v-if="record.recipientProjectName || record.receiver.known">
|
||||
<font-awesome
|
||||
:icon="record.recipientProjectName ? 'users' : 'user'"
|
||||
class="fa-fw text-slate-400"
|
||||
/>
|
||||
{{ record.recipientProjectName || record.receiver.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -175,14 +149,13 @@
|
||||
{{ description }}
|
||||
</a>
|
||||
</p>
|
||||
<p class="text-sm">{{ subDescription }}</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 text-lg bg-slate-300 rounded-b-md px-3 sm:px-4 py-1 sm:py-2"
|
||||
>
|
||||
<a class="cursor-pointer" @click="$emit('loadClaim', record.jwtId)">
|
||||
<fa icon="circle-info" class="fa-fw text-slate-500" />
|
||||
<font-awesome icon="circle-info" class="fa-fw text-slate-500" />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
@@ -220,53 +193,6 @@ export default class ActivityListItem extends Vue {
|
||||
return amount;
|
||||
}
|
||||
|
||||
private formatParticipantInfo(): string {
|
||||
const { giver, receiver } = this.record;
|
||||
|
||||
// Both participants are known contacts
|
||||
if (giver.known && receiver.known) {
|
||||
return `${giver.displayName} gave to ${receiver.displayName}`;
|
||||
}
|
||||
|
||||
// Only giver is known
|
||||
if (giver.known) {
|
||||
const recipient = this.record.recipientProjectName
|
||||
? `the project "${this.record.recipientProjectName}"`
|
||||
: receiver.displayName;
|
||||
return `${giver.displayName} gave to ${recipient}`;
|
||||
}
|
||||
|
||||
// Only receiver is known
|
||||
if (receiver.known) {
|
||||
const provider = this.record.providerPlanName
|
||||
? `the project "${this.record.providerPlanName}"`
|
||||
: giver.displayName;
|
||||
return `${receiver.displayName} received from ${provider}`;
|
||||
}
|
||||
|
||||
// Neither is known
|
||||
return this.formatUnknownParticipants();
|
||||
}
|
||||
|
||||
private formatUnknownParticipants(): string {
|
||||
const { giver, receiver, providerPlanName, recipientProjectName } =
|
||||
this.record;
|
||||
|
||||
if (providerPlanName || recipientProjectName) {
|
||||
const from = providerPlanName
|
||||
? `the project "${providerPlanName}"`
|
||||
: giver.displayName;
|
||||
const to = recipientProjectName
|
||||
? `the project "${recipientProjectName}"`
|
||||
: receiver.displayName;
|
||||
return `from ${from} to ${to}`;
|
||||
}
|
||||
|
||||
return giver.displayName === receiver.displayName
|
||||
? `between two who are ${giver.displayName}`
|
||||
: `from ${giver.displayName} to ${receiver.displayName}`;
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
const claim =
|
||||
(this.record.fullClaim as unknown).claim || this.record.fullClaim;
|
||||
@@ -278,12 +204,6 @@ export default class ActivityListItem extends Vue {
|
||||
return `${claim.description}`;
|
||||
}
|
||||
|
||||
get subDescription(): string {
|
||||
const participants = this.formatParticipantInfo();
|
||||
|
||||
return `${participants}`;
|
||||
}
|
||||
|
||||
private displayAmount(code: string, amt: number) {
|
||||
return `${amt} ${this.currencyShortWordForCode(code, amt === 1)}`;
|
||||
}
|
||||
@@ -292,11 +212,6 @@ export default class ActivityListItem extends Vue {
|
||||
return unitCode === "HUR" ? (single ? "hour" : "hours") : unitCode;
|
||||
}
|
||||
|
||||
get formattedTimestamp() {
|
||||
// Add your timestamp formatting logic here
|
||||
return this.record.timestamp;
|
||||
}
|
||||
|
||||
get canConfirm(): boolean {
|
||||
if (!this.isRegistered) return false;
|
||||
if (!isGiveClaimType(this.record.fullClaim?.["@type"])) return false;
|
||||
|
||||
@@ -1,29 +1,45 @@
|
||||
<template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="w-fit" v-html="generateIcon()"></div>
|
||||
<div class="w-fit">
|
||||
<font-awesome
|
||||
v-if="!this.contact?.did && !this.entityId"
|
||||
icon="person-circle-question"
|
||||
:class="`fa-fw text-slate-400`"
|
||||
:style="{ width: `${this.iconSize}px`, height: `${this.iconSize}px` }"
|
||||
/>
|
||||
<font-awesome
|
||||
v-else-if="isHiddenDid"
|
||||
icon="eye-slash"
|
||||
:class="`fa-fw text-slate-400`"
|
||||
:style="{ width: `${this.iconSize}px`, height: `${this.iconSize}px` }"
|
||||
/>
|
||||
<div v-else v-html="generateIcon()"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { createAvatar, StyleOptions } from "@dicebear/core";
|
||||
import { avataaars } from "@dicebear/collection";
|
||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import { isHiddenDid } from "@/libs/endorserServer";
|
||||
|
||||
@Component
|
||||
export default class EntityIcon extends Vue {
|
||||
@Prop contact: Contact;
|
||||
@Prop contact: Contact = null;
|
||||
@Prop entityId = ""; // overridden by contact.did or profileImageUrl
|
||||
@Prop iconSize = 0;
|
||||
@Prop profileImageUrl = ""; // overridden by contact.profileImageUrl
|
||||
|
||||
get isHiddenDid() {
|
||||
const identifier = this.contact?.did || this.entityId;
|
||||
return isHiddenDid(identifier);
|
||||
}
|
||||
|
||||
generateIcon() {
|
||||
const imageUrl = this.contact?.profileImageUrl || this.profileImageUrl;
|
||||
if (imageUrl) {
|
||||
return `<img src="${imageUrl}" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
|
||||
} else {
|
||||
const identifier = this.contact?.did || this.entityId;
|
||||
if (!identifier) {
|
||||
return `<img src="../src/assets/blank-square.svg" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
|
||||
}
|
||||
// https://api.dicebear.com/8.x/avataaars/svg?seed=
|
||||
// ... does not render things with the same seed as this library.
|
||||
// "did:ethr:0x222BB77E6Ff3774d34c751f3c1260866357B677b" yields a girl with flowers in her hair and a lightning earring
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
:src="imageUrl"
|
||||
class="max-h-[calc(100vh-5rem)] w-full h-full object-contain"
|
||||
alt="expanded shared content"
|
||||
@click.stop
|
||||
@click="close"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -112,7 +112,6 @@ db.on("populate", async () => {
|
||||
// ended up throwing lots of errors to the user... and they'd end up in a state
|
||||
// where they couldn't take action because they couldn't unlock that identity.)
|
||||
|
||||
// check for the secret in storage
|
||||
async function useSecretAndInitializeAccountsDB(
|
||||
secretDB: SecretDexie,
|
||||
accountsDB: SensitiveDexie,
|
||||
@@ -214,6 +213,22 @@ export async function updateAccountSettings(
|
||||
}
|
||||
}
|
||||
|
||||
export async function logToDb(message: string): Promise<void> {
|
||||
await db.open();
|
||||
const todayKey = new Date().toDateString();
|
||||
// only keep one day's worth of logs
|
||||
const previous = await db.logs.get(todayKey);
|
||||
if (!previous) {
|
||||
// when this is today's first log, clear out everything previous
|
||||
// to avoid the log table getting too large
|
||||
// (let's limit a different way someday)
|
||||
await db.logs.clear();
|
||||
}
|
||||
const prevMessages = (previous && previous.message) || "";
|
||||
const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`;
|
||||
await db.logs.update(todayKey, { message: fullMessage });
|
||||
}
|
||||
|
||||
// similar method is in the sw_scripts/additional-scripts.js file
|
||||
export async function logConsoleAndDb(
|
||||
message: string,
|
||||
@@ -224,16 +239,5 @@ export async function logConsoleAndDb(
|
||||
} else {
|
||||
logger.log(`${new Date().toISOString()} ${message}`);
|
||||
}
|
||||
|
||||
await db.open();
|
||||
const todayKey = new Date().toDateString();
|
||||
// only keep one day's worth of logs
|
||||
const previous = await db.logs.get(todayKey);
|
||||
if (!previous) {
|
||||
// when this is today's first log, clear out everything previous
|
||||
await db.logs.clear();
|
||||
}
|
||||
const prevMessages = (previous && previous.message) || "";
|
||||
const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`;
|
||||
await db.logs.update(todayKey, { message: fullMessage });
|
||||
await logToDb(message);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,6 @@ import {
|
||||
faUser,
|
||||
faUsers,
|
||||
faXmark,
|
||||
faBuilding,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
library.add(
|
||||
@@ -168,7 +167,6 @@ library.add(
|
||||
faUser,
|
||||
faUsers,
|
||||
faXmark,
|
||||
faBuilding,
|
||||
);
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
export interface GiveRecordWithContactInfo {
|
||||
import { GiveSummaryRecord, GiveVerifiableCredential } from "interfaces";
|
||||
|
||||
export interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
||||
jwtId: string;
|
||||
fullClaim: unknown; // Replace with proper type
|
||||
fullClaim: GiveVerifiableCredential;
|
||||
giver: {
|
||||
known: boolean;
|
||||
displayName: string;
|
||||
profileImageUrl?: string;
|
||||
};
|
||||
issuer: {
|
||||
known: boolean;
|
||||
displayName: string;
|
||||
profileImageUrl?: string;
|
||||
};
|
||||
receiver: {
|
||||
known: boolean;
|
||||
displayName: string;
|
||||
@@ -13,8 +20,6 @@ export interface GiveRecordWithContactInfo {
|
||||
};
|
||||
providerPlanName?: string;
|
||||
recipientProjectName?: string;
|
||||
description?: string;
|
||||
subDescription?: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,46 @@
|
||||
import { logToDb } from "../db";
|
||||
|
||||
function safeStringify(obj: unknown) {
|
||||
const seen = new WeakSet();
|
||||
|
||||
return JSON.stringify(obj, (key, value) => {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return "[Circular]";
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
|
||||
if (typeof value === "function") {
|
||||
return `[Function: ${value.name || "anonymous"}]`;
|
||||
}
|
||||
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
export const logger = {
|
||||
log: (message: string, ...args: unknown[]) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(message, ...args);
|
||||
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
||||
logToDb(message + argsString);
|
||||
}
|
||||
},
|
||||
warn: (message: string, ...args: unknown[]) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message, ...args);
|
||||
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
||||
logToDb(message + argsString);
|
||||
}
|
||||
},
|
||||
error: (message: string, ...args: unknown[]) => {
|
||||
// Errors will always be logged
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message, ...args); // Errors should always be logged
|
||||
console.error(message, ...args);
|
||||
const argsString = args.length > 0 ? " - " + safeStringify(args) : "";
|
||||
logToDb(message + argsString);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -196,14 +196,14 @@
|
||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
|
||||
@click="openFeedFilters()"
|
||||
>
|
||||
<fa icon="filter" class="fa-fw" />
|
||||
<font-awesome icon="filter" class="fa-fw" />
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] px-3 py-1.5 rounded-md text-xs text-white"
|
||||
@click="openFeedFilters()"
|
||||
>
|
||||
<fa icon="filter" class="fa-fw" />
|
||||
<font-awesome icon="filter" class="fa-fw" />
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
@@ -341,25 +341,10 @@ import {
|
||||
} from "../libs/util";
|
||||
import { GiveSummaryRecord } from "../interfaces";
|
||||
import * as serverUtil from "../libs/endorserServer";
|
||||
// import { fa0 } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
||||
jwtId: string;
|
||||
giver: {
|
||||
displayName: string;
|
||||
known: boolean;
|
||||
profileImageUrl?: string;
|
||||
};
|
||||
image?: string;
|
||||
providerPlanName?: string;
|
||||
recipientProjectName?: string;
|
||||
receiver: {
|
||||
displayName: string;
|
||||
known: boolean;
|
||||
profileImageUrl?: string;
|
||||
};
|
||||
}
|
||||
import { logger } from "../utils/logger";
|
||||
import { GiveRecordWithContactInfo } from "types";
|
||||
|
||||
|
||||
/**
|
||||
* HomeView - Main view component for the application's home page
|
||||
*
|
||||
@@ -507,31 +492,6 @@ export default class HomeView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
// this returns a Promise but we don't need to wait for it
|
||||
this.updateAllFeed();
|
||||
|
||||
if (this.activeDid) {
|
||||
const offersToUserData = await getNewOffersToUser(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
this.activeDid,
|
||||
this.lastAckedOfferToUserJwtId,
|
||||
);
|
||||
this.numNewOffersToUser = offersToUserData.data.length;
|
||||
this.newOffersToUserHitLimit = offersToUserData.hitLimit;
|
||||
}
|
||||
|
||||
if (this.activeDid) {
|
||||
const offersToUserProjects = await getNewOffersToUserProjects(
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
this.activeDid,
|
||||
this.lastAckedOfferToUserProjectsJwtId,
|
||||
);
|
||||
this.numNewOffersToUserProjects = offersToUserProjects.data.length;
|
||||
this.newOffersToUserProjectsHitLimit = offersToUserProjects.hitLimit;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
logConsoleAndDb("Error retrieving settings or feed: " + err, true);
|
||||
@@ -821,6 +781,12 @@ export default class HomeView extends Vue {
|
||||
this.allMyDids,
|
||||
),
|
||||
image: claim.image,
|
||||
issuer: didInfoForContact(
|
||||
record.issuerDid,
|
||||
this.activeDid,
|
||||
contactForDid(record.issuerDid, this.allContacts),
|
||||
this.allMyDids,
|
||||
),
|
||||
providerPlanHandleId: provider?.identifier as string,
|
||||
providerPlanName: providedByPlan?.name as string,
|
||||
recipientProjectName: fulfillsPlan?.name as string,
|
||||
|
||||
Reference in New Issue
Block a user