|
@ -122,10 +122,10 @@ |
|
|
@click="setVisibility(contact, false)" |
|
|
@click="setVisibility(contact, false)" |
|
|
> |
|
|
> |
|
|
<fa icon="eye" class="text-slate-900 fa-fw ml-1" /> |
|
|
<fa icon="eye" class="text-slate-900 fa-fw ml-1" /> |
|
|
<span class="tooltiptext">Can see you</span> |
|
|
<span class="tooltiptext">They can see you</span> |
|
|
</button> |
|
|
</button> |
|
|
<button v-else class="tooltip" @click="setVisibility(contact, true)"> |
|
|
<button v-else class="tooltip" @click="setVisibility(contact, true)"> |
|
|
<span class="tooltiptext">Cannot see you</span> |
|
|
<span class="tooltiptext">They cannot see you</span> |
|
|
<fa icon="eye-slash" class="text-slate-900 fa-fw ml-1" /> |
|
|
<fa icon="eye-slash" class="text-slate-900 fa-fw ml-1" /> |
|
|
</button> |
|
|
</button> |
|
|
|
|
|
|
|
@ -135,11 +135,11 @@ |
|
|
</button> |
|
|
</button> |
|
|
|
|
|
|
|
|
<button v-if="contact.registered" class="tooltip"> |
|
|
<button v-if="contact.registered" class="tooltip"> |
|
|
<span class="tooltiptext">Registered</span> |
|
|
<span class="tooltiptext">They are registered</span> |
|
|
<fa icon="person-circle-check" class="text-slate-900 fa-fw ml-1" /> |
|
|
<fa icon="person-circle-check" class="text-slate-900 fa-fw ml-1" /> |
|
|
</button> |
|
|
</button> |
|
|
<button v-else @click="register(contact)" class="tooltip"> |
|
|
<button v-else @click="register(contact)" class="tooltip"> |
|
|
<span class="tooltiptext">Maybe not registered</span> |
|
|
<span class="tooltiptext">They may not be registered</span> |
|
|
<fa |
|
|
<fa |
|
|
icon="person-circle-question" |
|
|
icon="person-circle-question" |
|
|
class="text-slate-900 fa-fw ml-1" |
|
|
class="text-slate-900 fa-fw ml-1" |
|
@ -165,9 +165,9 @@ |
|
|
: (givenByMeUnconfirmed[contact.did] || 0) |
|
|
: (givenByMeUnconfirmed[contact.did] || 0) |
|
|
/* eslint-enable prettier/prettier */ |
|
|
/* eslint-enable prettier/prettier */ |
|
|
}} |
|
|
}} |
|
|
<span class="tooltiptext-left">{{ |
|
|
<span class="tooltiptext-left"> |
|
|
givenByMeDescriptions[contact.did] |
|
|
{{ givenByMeDescriptions[contact.did] || "Nothing" }} |
|
|
}}</span> |
|
|
</span> |
|
|
<button |
|
|
<button |
|
|
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6" |
|
|
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6" |
|
|
@click="onClickAddGive(identity.did, contact.did)" |
|
|
@click="onClickAddGive(identity.did, contact.did)" |
|
@ -188,7 +188,7 @@ |
|
|
/* eslint-enable prettier/prettier */ |
|
|
/* eslint-enable prettier/prettier */ |
|
|
}} |
|
|
}} |
|
|
<span class="tooltiptext-left"> |
|
|
<span class="tooltiptext-left"> |
|
|
{{ givenToMeDescriptions[contact.did] }} |
|
|
{{ givenToMeDescriptions[contact.did] || "Nothing" }} |
|
|
</span> |
|
|
</span> |
|
|
<button |
|
|
<button |
|
|
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6" |
|
|
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6" |
|
@ -197,6 +197,16 @@ |
|
|
+ |
|
|
+ |
|
|
</button> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<router-link |
|
|
|
|
|
:to="{ |
|
|
|
|
|
name: 'contact-amounts', |
|
|
|
|
|
query: { contactDid: contact.did }, |
|
|
|
|
|
}" |
|
|
|
|
|
class="tooltip" |
|
|
|
|
|
> |
|
|
|
|
|
<fa icon="file-lines" class="text-slate-600 fa-fw ml-1" /> |
|
|
|
|
|
<span class="tooltiptext-left">See All Given Activity</span> |
|
|
|
|
|
</router-link> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
@ -223,44 +233,20 @@ import { IIdentifier } from "@veramo/core"; |
|
|
import { Options, Vue } from "vue-class-component"; |
|
|
import { Options, Vue } from "vue-class-component"; |
|
|
|
|
|
|
|
|
import { AppString } from "@/constants/app"; |
|
|
import { AppString } from "@/constants/app"; |
|
|
import { accessToken, SimpleSigner } from "@/libs/crypto"; |
|
|
|
|
|
import { accountsDB, db } from "@/db"; |
|
|
import { accountsDB, db } from "@/db"; |
|
|
import { Contact } from "@/db/tables/contacts"; |
|
|
import { Contact } from "@/db/tables/contacts"; |
|
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; |
|
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; |
|
|
|
|
|
import { accessToken, SimpleSigner } from "@/libs/crypto"; |
|
|
|
|
|
import { |
|
|
|
|
|
GiveServerRecord, |
|
|
|
|
|
GiveVerifiableCredential, |
|
|
|
|
|
RegisterVerifiableCredential, |
|
|
|
|
|
SERVICE_ID, |
|
|
|
|
|
} from "@/libs/endorserServer"; |
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires |
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires |
|
|
const Buffer = require("buffer/").Buffer; |
|
|
const Buffer = require("buffer/").Buffer; |
|
|
|
|
|
|
|
|
const SERVICE_ID = "endorser.ch"; |
|
|
|
|
|
|
|
|
|
|
|
export interface GiveServerRecord { |
|
|
|
|
|
agentDid: string; |
|
|
|
|
|
amount: number; |
|
|
|
|
|
confirmed: number; |
|
|
|
|
|
description: string; |
|
|
|
|
|
fullClaim: GiveVerifiableCredential; |
|
|
|
|
|
handleId: string; |
|
|
|
|
|
recipientDid: string; |
|
|
|
|
|
unit: string; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export interface GiveVerifiableCredential { |
|
|
|
|
|
"@context": string; |
|
|
|
|
|
"@type": string; |
|
|
|
|
|
agent: { identifier: string }; |
|
|
|
|
|
description?: string; |
|
|
|
|
|
object: { amountOfThisGood: number; unitCode: string }; |
|
|
|
|
|
recipient: { identifier: string }; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export interface RegisterVerifiableCredential { |
|
|
|
|
|
"@context": string; |
|
|
|
|
|
"@type": string; |
|
|
|
|
|
agent: { identifier: string }; |
|
|
|
|
|
object: string; |
|
|
|
|
|
recipient: { identifier: string }; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Options({ |
|
|
@Options({ |
|
|
components: {}, |
|
|
components: {}, |
|
|
}) |
|
|
}) |
|
@ -305,33 +291,6 @@ export default class ContactsView extends Vue { |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async onClickNewContact(): Promise<void> { |
|
|
|
|
|
let did = this.contactInput; |
|
|
|
|
|
let name, publicKeyBase64; |
|
|
|
|
|
const commaPos1 = this.contactInput.indexOf(","); |
|
|
|
|
|
if (commaPos1 > -1) { |
|
|
|
|
|
did = this.contactInput.substring(0, commaPos1).trim(); |
|
|
|
|
|
name = this.contactInput.substring(commaPos1 + 1).trim(); |
|
|
|
|
|
const commaPos2 = this.contactInput.indexOf(",", commaPos1 + 1); |
|
|
|
|
|
if (commaPos2 > -1) { |
|
|
|
|
|
name = this.contactInput.substring(commaPos1 + 1, commaPos2).trim(); |
|
|
|
|
|
publicKeyBase64 = this.contactInput.substring(commaPos2 + 1).trim(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// help with potential mistakes while this sharing requires copy-and-paste |
|
|
|
|
|
if (publicKeyBase64 && /^[0-9A-Fa-f]{66}$/i.test(publicKeyBase64)) { |
|
|
|
|
|
// it must be all hex (compressed public key), so convert |
|
|
|
|
|
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64"); |
|
|
|
|
|
} |
|
|
|
|
|
const newContact = { did, name, publicKeyBase64 }; |
|
|
|
|
|
await db.contacts.add(newContact); |
|
|
|
|
|
const allContacts = this.contacts.concat([newContact]); |
|
|
|
|
|
this.contacts = R.sort( |
|
|
|
|
|
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""), |
|
|
|
|
|
allContacts |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async loadGives() { |
|
|
async loadGives() { |
|
|
if (!this.identity) { |
|
|
if (!this.identity) { |
|
|
console.error( |
|
|
console.error( |
|
@ -346,7 +305,7 @@ export default class ContactsView extends Vue { |
|
|
try { |
|
|
try { |
|
|
const url = |
|
|
const url = |
|
|
endorserApiServer + |
|
|
endorserApiServer + |
|
|
"/api/v2/report/gives?agentId=" + |
|
|
"/api/v2/report/gives?agentDid=" + |
|
|
encodeURIComponent(this.identity?.did); |
|
|
encodeURIComponent(this.identity?.did); |
|
|
const token = await accessToken(this.identity); |
|
|
const token = await accessToken(this.identity); |
|
|
const headers = { |
|
|
const headers = { |
|
@ -400,7 +359,7 @@ export default class ContactsView extends Vue { |
|
|
try { |
|
|
try { |
|
|
const url = |
|
|
const url = |
|
|
endorserApiServer + |
|
|
endorserApiServer + |
|
|
"/api/v2/report/gives?recipientId=" + |
|
|
"/api/v2/report/gives?recipientDid=" + |
|
|
encodeURIComponent(this.identity.did); |
|
|
encodeURIComponent(this.identity.did); |
|
|
const token = await accessToken(this.identity); |
|
|
const token = await accessToken(this.identity); |
|
|
const headers = { |
|
|
const headers = { |
|
@ -450,6 +409,33 @@ export default class ContactsView extends Vue { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async onClickNewContact(): Promise<void> { |
|
|
|
|
|
let did = this.contactInput; |
|
|
|
|
|
let name, publicKeyBase64; |
|
|
|
|
|
const commaPos1 = this.contactInput.indexOf(","); |
|
|
|
|
|
if (commaPos1 > -1) { |
|
|
|
|
|
did = this.contactInput.substring(0, commaPos1).trim(); |
|
|
|
|
|
name = this.contactInput.substring(commaPos1 + 1).trim(); |
|
|
|
|
|
const commaPos2 = this.contactInput.indexOf(",", commaPos1 + 1); |
|
|
|
|
|
if (commaPos2 > -1) { |
|
|
|
|
|
name = this.contactInput.substring(commaPos1 + 1, commaPos2).trim(); |
|
|
|
|
|
publicKeyBase64 = this.contactInput.substring(commaPos2 + 1).trim(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// help with potential mistakes while this sharing requires copy-and-paste |
|
|
|
|
|
if (publicKeyBase64 && /^[0-9A-Fa-f]{66}$/i.test(publicKeyBase64)) { |
|
|
|
|
|
// it must be all hex (compressed public key), so convert |
|
|
|
|
|
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64"); |
|
|
|
|
|
} |
|
|
|
|
|
const newContact = { did, name, publicKeyBase64 }; |
|
|
|
|
|
await db.contacts.add(newContact); |
|
|
|
|
|
const allContacts = this.contacts.concat([newContact]); |
|
|
|
|
|
this.contacts = R.sort( |
|
|
|
|
|
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""), |
|
|
|
|
|
allContacts |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
async deleteContact(contact: Contact) { |
|
|
async deleteContact(contact: Contact) { |
|
|
if ( |
|
|
if ( |
|
|
confirm( |
|
|
confirm( |
|
@ -647,6 +633,22 @@ export default class ContactsView extends Vue { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async onClickAddGive(fromDid: string, toDid: string): Promise<void> { |
|
|
async onClickAddGive(fromDid: string, toDid: string): Promise<void> { |
|
|
|
|
|
// if they have unconfirmed amounts, ask to confirm those first |
|
|
|
|
|
if (toDid == this.identity?.did && this.givenToMeUnconfirmed[fromDid] > 0) { |
|
|
|
|
|
if ( |
|
|
|
|
|
confirm( |
|
|
|
|
|
"There are " + |
|
|
|
|
|
this.givenToMeUnconfirmed[fromDid] + |
|
|
|
|
|
" unconfirmed hours from them." + |
|
|
|
|
|
" Would you like to confirm some of those hours?" |
|
|
|
|
|
) |
|
|
|
|
|
) { |
|
|
|
|
|
this.$router.push({ |
|
|
|
|
|
name: "contact-amounts", |
|
|
|
|
|
query: { contactDid: fromDid }, |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
if (!this.isNumeric(this.hourInput)) { |
|
|
if (!this.isNumeric(this.hourInput)) { |
|
|
this.alertTitle = "Input Error"; |
|
|
this.alertTitle = "Input Error"; |
|
|
this.alertMessage = |
|
|
this.alertMessage = |
|
@ -661,61 +663,36 @@ export default class ContactsView extends Vue { |
|
|
this.alertMessage = "No identity is available."; |
|
|
this.alertMessage = "No identity is available."; |
|
|
this.isAlertVisible = true; |
|
|
this.isAlertVisible = true; |
|
|
} else { |
|
|
} else { |
|
|
// if they have unconfirmed amounts, ask to confirm those first |
|
|
// ask to confirm amount |
|
|
let wantsToConfirm = false; |
|
|
let toFrom; |
|
|
if ( |
|
|
if (fromDid == this.identity?.did) { |
|
|
toDid == this.identity?.did && |
|
|
toFrom = "from you to " + this.nameForDid(this.contacts, toDid); |
|
|
this.givenToMeUnconfirmed[fromDid] > 0 |
|
|
} else { |
|
|
) { |
|
|
toFrom = "from " + this.nameForDid(this.contacts, fromDid) + " to you"; |
|
|
if ( |
|
|
|
|
|
confirm( |
|
|
|
|
|
"There are " + |
|
|
|
|
|
this.givenToMeUnconfirmed[fromDid] + |
|
|
|
|
|
" unconfirmed hours from them." + |
|
|
|
|
|
" Would you like to confirm some of those hours?" |
|
|
|
|
|
) |
|
|
|
|
|
) { |
|
|
|
|
|
wantsToConfirm = true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
if (wantsToConfirm) { |
|
|
let description; |
|
|
this.$router.push({ |
|
|
if (this.hourDescriptionInput) { |
|
|
name: "contact-amounts", |
|
|
description = " with description '" + this.hourDescriptionInput + "'"; |
|
|
query: { contactDid: fromDid }, |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
// ask to confirm amount |
|
|
description = " with no description"; |
|
|
let toFrom; |
|
|
} |
|
|
if (fromDid == this.identity?.did) { |
|
|
if ( |
|
|
toFrom = "from you to " + this.nameForDid(this.contacts, toDid); |
|
|
confirm( |
|
|
} else { |
|
|
"Are you sure you want to record " + |
|
|
toFrom = |
|
|
this.hourInput + |
|
|
"from " + this.nameForDid(this.contacts, fromDid) + " to you"; |
|
|
" hours " + |
|
|
} |
|
|
toFrom + |
|
|
let description; |
|
|
description + |
|
|
if (this.hourDescriptionInput) { |
|
|
"?" |
|
|
description = " with description '" + this.hourDescriptionInput + "'"; |
|
|
) |
|
|
} else { |
|
|
) { |
|
|
description = " with no description"; |
|
|
this.createAndSubmitGive( |
|
|
} |
|
|
this.identity, |
|
|
if ( |
|
|
fromDid, |
|
|
confirm( |
|
|
toDid, |
|
|
"Are you sure you want to record " + |
|
|
parseFloat(this.hourInput), |
|
|
this.hourInput + |
|
|
this.hourDescriptionInput |
|
|
" hours " + |
|
|
); |
|
|
toFrom + |
|
|
|
|
|
description + |
|
|
|
|
|
"?" |
|
|
|
|
|
) |
|
|
|
|
|
) { |
|
|
|
|
|
this.createAndSubmitGive( |
|
|
|
|
|
this.identity, |
|
|
|
|
|
fromDid, |
|
|
|
|
|
toDid, |
|
|
|
|
|
parseFloat(this.hourInput), |
|
|
|
|
|
this.hourDescriptionInput |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|