forked from trent_larson/crowd-funder-for-time-pwa
add ability to give to fulfill an offer; adjust visibility of claim actions
This commit is contained in:
@@ -20,8 +20,6 @@ npm run lint
|
||||
|
||||
### Compiles and minifies for production
|
||||
|
||||
If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
||||
|
||||
* Update the project.task.yaml & CHANGELOG.md & the version in package.json, run `npm install`, and commit.
|
||||
|
||||
* [Tag wth the new version.](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases)
|
||||
@@ -44,7 +42,7 @@ If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js,
|
||||
|
||||
* `rsync -azvu -e "ssh -i ~/.ssh/..." dist ubuntutest@test.timesafari.app:time-safari`
|
||||
|
||||
* Revert src/constants/app.ts and package.json (if that was prod), edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
|
||||
* Revert src/constants/app.ts and package.json (if that was prod), edit package.json to increment version & add "-beta", `npm install`, and commit. Tag if you didn't before. Also record what version is on production.
|
||||
|
||||
|
||||
|
||||
@@ -135,6 +133,8 @@ To add an icon, add to main.ts and reference with `fa` element and `icon` attrib
|
||||
|
||||
* [Customize Vue configuration](https://cli.vuejs.org/config/).
|
||||
|
||||
* If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
|
||||
|
||||
|
||||
### Kudos
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
|
||||
tasks:
|
||||
|
||||
- update dependencies, especially Veramo
|
||||
|
||||
- record donations vs gives
|
||||
- deploy & migrate
|
||||
- in mobile - change give & fulfills to array of objects?
|
||||
- deploy & migrate (prod)
|
||||
- update docs
|
||||
|
||||
- check that 'show more contacts' from the contact-give-list on the project screen includes project ID
|
||||
- on ClaimView, the "ask someone" should refer to "visible" IDs, or to confirmations only if confirmations are visible
|
||||
- "send them to this page" on ClaimView should be a link for installed app
|
||||
|
||||
- show VC details... somehow:
|
||||
- 01 show my VCs - most interesting, or via search
|
||||
- 04 allow user to download & prove chains of VCs, mine + ones I can see about me from others
|
||||
@@ -15,7 +16,9 @@ tasks:
|
||||
- on gives feed - link to project
|
||||
- show feed of offers, new projects, etc -- maybe limited to my search area
|
||||
|
||||
- revenue
|
||||
- update Veramo library
|
||||
|
||||
- revenue to support server operation
|
||||
|
||||
- copy button for seed
|
||||
- .5 If notifications are not enabled, add message to front page with link/button to enable
|
||||
|
||||
@@ -67,10 +67,15 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||
import { createAndSubmitGive, GiverInputInfo } from "@/libs/endorserServer";
|
||||
import {
|
||||
createAndSubmitGive,
|
||||
didInfo,
|
||||
GiverInputInfo,
|
||||
} from "@/libs/endorserServer";
|
||||
import { accountsDB, db } from "@/db/index";
|
||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||
import { Account } from "@/db/tables/accounts";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
|
||||
interface Notification {
|
||||
group: string;
|
||||
@@ -85,9 +90,12 @@ export default class GiftedDialog extends Vue {
|
||||
|
||||
@Prop message = "";
|
||||
@Prop projectId = "";
|
||||
@Prop offerId = "";
|
||||
@Prop showGivenToUser = false;
|
||||
|
||||
activeDid = "";
|
||||
allContacts: Array<Contact> = [];
|
||||
allMyDids: Array<string> = [];
|
||||
apiServer = "";
|
||||
|
||||
amountInput = "0";
|
||||
@@ -122,9 +130,16 @@ export default class GiftedDialog extends Vue {
|
||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||||
this.apiServer = settings?.apiServer || "";
|
||||
this.activeDid = settings?.activeDid || "";
|
||||
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
|
||||
await accountsDB.open();
|
||||
const allAccounts = await accountsDB.accounts.toArray();
|
||||
this.allMyDids = allAccounts.map((acc) => acc.did);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
console.log("Error retrieving settings from database:", err);
|
||||
console.error("Error retrieving settings from database:", err);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -140,6 +155,14 @@ export default class GiftedDialog extends Vue {
|
||||
open(giver: GiverInputInfo) {
|
||||
this.description = "";
|
||||
this.giver = giver;
|
||||
if (!this.giver.name) {
|
||||
this.giver.name = didInfo(
|
||||
this.giver.did,
|
||||
this.activeDid,
|
||||
this.allMyDids,
|
||||
this.allContacts,
|
||||
);
|
||||
}
|
||||
// if we show "given to user" selection, default checkbox to true
|
||||
this.givenToUser = this.showGivenToUser;
|
||||
this.amountInput = "0";
|
||||
@@ -271,6 +294,7 @@ export default class GiftedDialog extends Vue {
|
||||
amountInput,
|
||||
unitCode,
|
||||
this.projectId,
|
||||
this.offerId,
|
||||
this.isTrade,
|
||||
);
|
||||
|
||||
@@ -279,7 +303,7 @@ export default class GiftedDialog extends Vue {
|
||||
this.isGiveCreationError(result.response)
|
||||
) {
|
||||
const errorMessage = this.getGiveCreationErrorMessage(result);
|
||||
console.log("Error with give creation result:", result);
|
||||
console.error("Error with give creation result:", result);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -302,7 +326,7 @@ export default class GiftedDialog extends Vue {
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
console.log("Error with give recordation caught:", error);
|
||||
console.error("Error with give recordation caught:", error);
|
||||
const message =
|
||||
error.userMessage ||
|
||||
error.response?.data?.error?.message ||
|
||||
|
||||
@@ -52,6 +52,7 @@ export interface GenericServerRecord extends GenericVerifiableCredential {
|
||||
issuer?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
claim: Record<any, any>;
|
||||
claimType?: string;
|
||||
}
|
||||
export const BLANK_GENERIC_SERVER_RECORD: GenericServerRecord = {
|
||||
"@context": SCHEMA_ORG_CONTEXT,
|
||||
@@ -259,8 +260,8 @@ export function removeVisibleToDids(input: any): any {
|
||||
Similar logic is found in endorser-mobile.
|
||||
**/
|
||||
export function didInfo(
|
||||
did: string,
|
||||
activeDid: string,
|
||||
did: string | undefined,
|
||||
activeDid: string | undefined,
|
||||
allMyDids: string[],
|
||||
contacts: Contact[],
|
||||
): string {
|
||||
@@ -312,6 +313,7 @@ export async function createAndSubmitGive(
|
||||
hours?: number,
|
||||
unitCode?: string,
|
||||
fulfillsProjectHandleId?: string,
|
||||
fulfillsOfferHandleId?: string,
|
||||
isTrade: boolean = false,
|
||||
): Promise<CreateAndSubmitClaimResult> {
|
||||
const vcClaim: GiveVerifiableCredential = {
|
||||
@@ -332,6 +334,13 @@ export async function createAndSubmitGive(
|
||||
identifier: fulfillsProjectHandleId,
|
||||
});
|
||||
}
|
||||
if (fulfillsOfferHandleId) {
|
||||
vcClaim.fulfills = vcClaim.fulfills || []; // weird that it won't typecheck without this
|
||||
vcClaim.fulfills.push({
|
||||
"@type": "Offer",
|
||||
identifier: fulfillsOfferHandleId,
|
||||
});
|
||||
}
|
||||
return createAndSubmitClaim(
|
||||
vcClaim as GenericServerRecord,
|
||||
identity,
|
||||
@@ -437,7 +446,7 @@ export async function createAndSubmitClaim(
|
||||
return { type: "success", response };
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
console.log("Error creating claim:", error);
|
||||
console.error("Error creating claim:", error);
|
||||
const errorMessage: string =
|
||||
error.response?.data?.error?.message || error.message || "Unknown error";
|
||||
|
||||
|
||||
@@ -17,34 +17,55 @@
|
||||
</div>
|
||||
|
||||
<!-- Details -->
|
||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||
<div>
|
||||
<div class="block flex gap-4 overflow-hidden">
|
||||
<div class="overflow-hidden">
|
||||
<h2 class="text-md font-bold">{{ veriClaim.id }}</h2>
|
||||
<div class="text-sm">
|
||||
<div>
|
||||
{{ veriClaim.claimType }}
|
||||
</div>
|
||||
<div>
|
||||
<fa icon="message" class="fa-fw text-slate-400"></fa>
|
||||
{{ veriClaim.claim?.description }}
|
||||
</div>
|
||||
<div>
|
||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||
{{ veriClaim.issuer }}
|
||||
</div>
|
||||
<div>
|
||||
<fa icon="calendar" class="fa-fw text-slate-400"></fa>
|
||||
{{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }}
|
||||
</div>
|
||||
<div class="h-32 bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||
<div class="block flex gap-4 overflow-hidden">
|
||||
<div class="overflow-hidden">
|
||||
<h2 class="text-md font-bold">{{ veriClaim.id }}</h2>
|
||||
<div class="text-sm">
|
||||
<div>
|
||||
{{ capitalizeAndInsertSpacesBeforeCaps(veriClaim.claimType) }}
|
||||
</div>
|
||||
<div>
|
||||
<fa icon="message" class="fa-fw text-slate-400"></fa>
|
||||
{{ veriClaim.claim?.description }}
|
||||
</div>
|
||||
<div>
|
||||
<fa icon="user" class="fa-fw text-slate-400"></fa>
|
||||
{{ veriClaim.issuer }}
|
||||
</div>
|
||||
<div>
|
||||
<fa icon="calendar" class="fa-fw text-slate-400"></fa>
|
||||
{{ veriClaim.issuedAt?.replace(/T/, " ").replace(/Z/, " UTC") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="h-6 columns-3">
|
||||
<button
|
||||
class="col-span-1 bg-blue-600 text-white px-4 py-2 rounded-md"
|
||||
v-if="userCanConfirm()"
|
||||
@click="confirmClaim(veriClaim.id)"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="canFulfillOffer()"
|
||||
@click="openGiftDialog()"
|
||||
class="col-span-1 block w-fit text-center text-md bg-blue-600 text-white px-1.5 py-2 rounded-md"
|
||||
>
|
||||
Record Some Delivered
|
||||
</button>
|
||||
</div>
|
||||
<GiftedDialog
|
||||
ref="customGiveDialog"
|
||||
message="Offer fulfilled by"
|
||||
:offerId="veriClaim.handleId"
|
||||
/>
|
||||
|
||||
<div v-if="isConfirmable()">
|
||||
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Confirmations</h2>
|
||||
|
||||
<span v-if="totalConfirmers() === 0">Nobody has confirmed this.</span>
|
||||
@@ -71,7 +92,7 @@
|
||||
</div>
|
||||
<div v-if="confirmerIdList.length > 0">
|
||||
The following people have issued or confirmed this claim.
|
||||
<ul>
|
||||
<ul class="ml-4">
|
||||
<li
|
||||
v-for="confirmerId in confirmerIdList"
|
||||
:key="confirmerId"
|
||||
@@ -98,7 +119,7 @@
|
||||
<div v-if="confsVisibleToIdList.length > 0">
|
||||
The following people can connect you with people who have issued or
|
||||
confirmed this claim.
|
||||
<ul>
|
||||
<ul class="ml-4">
|
||||
<li
|
||||
v-for="confsVisibleTo in confsVisibleToIdList"
|
||||
:key="confsVisibleTo"
|
||||
@@ -116,27 +137,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div v-if="confirmerIdList.includes(activeDid)">
|
||||
You have confirmed this claim.
|
||||
</div>
|
||||
<div v-else-if="containsHiddenDid(veriClaim.claim)">
|
||||
You cannot confirm this claim because it contains data that is hidden
|
||||
from you.
|
||||
</div>
|
||||
<div v-else>
|
||||
<button
|
||||
class="bg-blue-600 text-white mt-4 px-4 py-2 rounded-md mb-4"
|
||||
@click="confirmClaim(veriClaim.id)"
|
||||
>
|
||||
Confirm Claim
|
||||
</button>
|
||||
</div>
|
||||
<!-- explain if user cannot confirm -->
|
||||
<!-- Note that these conditions are mirrored in userCanConfirm(). -->
|
||||
<div v-if="confirmerIdList.includes(activeDid)">
|
||||
You have confirmed this claim.
|
||||
</div>
|
||||
<div v-else-if="veriClaim.issuer == activeDid">
|
||||
You cannot confirm this because you issued this claim, so you already
|
||||
count as confirming it.
|
||||
</div>
|
||||
<div v-else-if="containsHiddenDid(veriClaim.claim)">
|
||||
You cannot confirm this because it contains hidden identifiers.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Claim</h2>
|
||||
<h2 class="font-bold uppercase text-xl mt-8 mb-2">Visible Details</h2>
|
||||
<pre
|
||||
class="text-sm overflow-x-scroll px-4 py-3 bg-slate-100 rounded-md"
|
||||
>{{ veriClaimDump }}</pre
|
||||
@@ -192,6 +208,7 @@ import * as serverUtil from "@/libs/endorserServer";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import EntityIcon from "@/components/EntityIcon.vue";
|
||||
import { Account } from "@/db/tables/accounts";
|
||||
import { GiverInputInfo } from "@/libs/endorserServer";
|
||||
|
||||
interface Notification {
|
||||
group: string;
|
||||
@@ -210,9 +227,9 @@ export default class ClaimView extends Vue {
|
||||
allMyDids: Array<string> = [];
|
||||
allContacts: Array<Contact> = [];
|
||||
apiServer = "";
|
||||
confirmerIdList = []; // list of DIDs that have confirmed this claim excluding the issuer
|
||||
confirmerIdList: string[] = []; // list of DIDs that have confirmed this claim excluding the issuer
|
||||
confsVisibleErrorMessage = "";
|
||||
confsVisibleToIdList = []; // list of DIDs that can see any confirmer
|
||||
confsVisibleToIdList: string[] = []; // list of DIDs that can see any confirmer
|
||||
fullClaim = null;
|
||||
fullClaimDump = "";
|
||||
fullClaimMessage = "";
|
||||
@@ -242,7 +259,7 @@ export default class ClaimView extends Vue {
|
||||
let claimId;
|
||||
if (pathParam) {
|
||||
claimId = decodeURIComponent(pathParam);
|
||||
this.loadClaim(claimId, identity);
|
||||
await this.loadClaim(claimId, identity);
|
||||
} else {
|
||||
this.$notify(
|
||||
{
|
||||
@@ -256,6 +273,50 @@ export default class ClaimView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
// insert a space before any capital letters except the initial letter
|
||||
// (and capitalize initial letter, just in case)
|
||||
capitalizeAndInsertSpacesBeforeCaps(text: string) {
|
||||
return !text
|
||||
? ""
|
||||
: text[0].toUpperCase() + text.substr(1).replace(/([A-Z])/g, " $1");
|
||||
}
|
||||
|
||||
isConfirmable() {
|
||||
return this.veriClaim.claimType === "GiveAction";
|
||||
}
|
||||
|
||||
userCanConfirm() {
|
||||
// Note that this logic is mirrored in the template. Look for "userCanConfirm"
|
||||
return (
|
||||
this.isConfirmable() &&
|
||||
!this.confirmerIdList.includes(this.activeDid) &&
|
||||
this.veriClaim.issuer !== this.activeDid &&
|
||||
!this.containsHiddenDid(this.veriClaim.claim)
|
||||
);
|
||||
}
|
||||
|
||||
offerGiverDid(): string | undefined {
|
||||
let giver;
|
||||
if (
|
||||
this.veriClaim.claim.offeredBy?.identifier &&
|
||||
!serverUtil.isHiddenDid(
|
||||
this.veriClaim.claim.offeredBy.identifier as string,
|
||||
)
|
||||
) {
|
||||
giver = this.veriClaim.claim.offeredBy.identifier;
|
||||
} else if (
|
||||
this.veriClaim.issuer &&
|
||||
!serverUtil.isHiddenDid(this.veriClaim.issuer)
|
||||
) {
|
||||
giver = this.veriClaim.issuer;
|
||||
}
|
||||
return giver;
|
||||
}
|
||||
|
||||
canFulfillOffer() {
|
||||
return this.veriClaim.claimType === "Offer" && this.offerGiverDid();
|
||||
}
|
||||
|
||||
totalConfirmers() {
|
||||
return (
|
||||
this.numConfsNotVisible +
|
||||
@@ -313,7 +374,7 @@ export default class ClaimView extends Vue {
|
||||
this.veriClaimDump = yaml.dump(this.veriClaim);
|
||||
} else {
|
||||
// actually, axios typically throws an error so we never get here
|
||||
console.log("Error getting claim:", resp);
|
||||
console.error("Error getting claim:", resp);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -393,7 +454,7 @@ export default class ClaimView extends Vue {
|
||||
this.fullClaimDump = yaml.dump(this.fullClaim);
|
||||
} else {
|
||||
// actually, axios typically throws an error so we never get here
|
||||
console.log("Error getting full claim:", resp);
|
||||
console.error("Error getting full claim:", resp);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -466,7 +527,7 @@ export default class ClaimView extends Vue {
|
||||
5000,
|
||||
);
|
||||
} else {
|
||||
console.log("Got error submitting the confirmation:", result);
|
||||
console.error("Got error submitting the confirmation:", result);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -479,5 +540,12 @@ export default class ClaimView extends Vue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
openGiftDialog() {
|
||||
const giver: GiverInputInfo = {
|
||||
did: this.offerGiverDid(),
|
||||
};
|
||||
(this.$refs.customGiveDialog as GiftedDialog).open(giver);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -992,7 +992,7 @@ export default class ContactsView extends Vue {
|
||||
"?",
|
||||
)
|
||||
) {
|
||||
this.createAndSubmitGive(
|
||||
this.createAndSubmitContactGive(
|
||||
identity,
|
||||
fromDid,
|
||||
toDid,
|
||||
@@ -1004,7 +1004,7 @@ export default class ContactsView extends Vue {
|
||||
}
|
||||
|
||||
// similar function is in endorserServer.ts
|
||||
private async createAndSubmitGive(
|
||||
private async createAndSubmitContactGive(
|
||||
identity: IIdentifier,
|
||||
fromDid: string,
|
||||
toDid: string,
|
||||
@@ -1073,7 +1073,7 @@ export default class ContactsView extends Vue {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error in createAndSubmitGive: ", error);
|
||||
console.log("Error in createAndSubmitContactGive: ", error);
|
||||
let userMessage = "There was an error. See logs for more info.";
|
||||
const serverError = error as AxiosError;
|
||||
if (serverError) {
|
||||
|
||||
@@ -94,13 +94,14 @@
|
||||
<div v-if="activeDid" class="mb-4">
|
||||
<div class="text-center">
|
||||
<button
|
||||
@click="openOfferDialog({ name: 'you', did: activeDid })"
|
||||
@click="openOfferDialog()"
|
||||
class="block w-full text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
|
||||
>
|
||||
I offer…
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<OfferDialog ref="customOfferDialog" :projectId="this.projectId" />
|
||||
|
||||
<div v-if="activeDid">
|
||||
<div class="text-center">
|
||||
@@ -112,6 +113,12 @@
|
||||
</button>
|
||||
<p class="mt-2 mb-4 text-center">Or, record a contribution from:</p>
|
||||
</div>
|
||||
<GiftedDialog
|
||||
ref="customGiveDialog"
|
||||
message="Received from"
|
||||
:projectId="this.projectId"
|
||||
>
|
||||
</GiftedDialog>
|
||||
|
||||
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
||||
<li @click="openGiftDialog()">
|
||||
@@ -267,15 +274,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<GiftedDialog
|
||||
ref="customGiveDialog"
|
||||
message="Received from"
|
||||
:projectId="this.projectId"
|
||||
>
|
||||
</GiftedDialog>
|
||||
<OfferDialog ref="customOfferDialog" :projectId="this.projectId">
|
||||
</OfferDialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -434,7 +432,7 @@ export default class ProjectViewView extends Vue {
|
||||
this.url = resp.data.claim?.url || "";
|
||||
} else {
|
||||
// actually, axios throws an error on 404 so we probably never get here
|
||||
console.log("Error getting project:", resp);
|
||||
console.error("Error getting project:", resp);
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
|
||||
Reference in New Issue
Block a user