forked from trent_larson/crowd-funder-for-time-pwa
Merge remote-tracking branch 'original-origin/master' into feat/vitejs-trent
This commit is contained in:
@@ -112,6 +112,8 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
|
||||
<!-- label -->
|
||||
<div class="mb-2 font-bold">Settings</div>
|
||||
<div
|
||||
v-if="!notificationMaybeChanged"
|
||||
class="flex items-center justify-between cursor-pointer"
|
||||
@@ -140,16 +142,38 @@
|
||||
Notification status may have changed. Refresh this page to see the
|
||||
latest setting.
|
||||
</div>
|
||||
<router-link class="px-4 text-sm text-blue-500" to="/help-notifications">
|
||||
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
|
||||
Troubleshoot your notification setup.
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
:to="{ name: 'search-area' }"
|
||||
v-if="activeDid"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md mb-2 mt-6"
|
||||
>
|
||||
Set Search Area…
|
||||
<!-- If already set, change button label to "Change Search Area" -->
|
||||
</router-link>
|
||||
|
||||
<div class="text-slate-500 text-sm font-bold mt-6 mb-2">
|
||||
Topics of Interest
|
||||
</div>
|
||||
<textarea
|
||||
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||
rows="3"
|
||||
value="longing, rusted, seventeen, daybreak, furnace, nine, benign, homecoming, one, freight car"
|
||||
>
|
||||
</textarea>
|
||||
<div class="text-slate-500 text-sm mt-2 mb-2">
|
||||
Separate topics with a comma.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="activeDid"
|
||||
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
|
||||
>
|
||||
<div class="mb-2">Usage Limits</div>
|
||||
<div class="mb-2 font-bold">Usage Limits</div>
|
||||
<!-- show spinner if loading limits -->
|
||||
<div v-if="loadingLimits" class="text-center">
|
||||
Checking… <fa icon="spinner" class="fa-spin"></fa>
|
||||
@@ -200,7 +224,7 @@
|
||||
</div>
|
||||
|
||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
|
||||
<div>Data Export</div>
|
||||
<div class="mb-2 font-bold">Data Export</div>
|
||||
<router-link
|
||||
:to="{ name: 'seed-backup' }"
|
||||
v-if="activeDid"
|
||||
@@ -211,7 +235,7 @@
|
||||
|
||||
<button
|
||||
v-bind:class="computedStartDownloadLinkClassNames()"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md mb-6"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
@click="exportDatabase()"
|
||||
>
|
||||
Download Settings & Contacts
|
||||
@@ -221,7 +245,7 @@
|
||||
<a
|
||||
ref="downloadLink"
|
||||
v-bind:class="computedDownloadLinkClassNames()"
|
||||
class="block w-full text-center text-md uppercase bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||
class="block w-full text-center text-md uppercase bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
|
||||
>
|
||||
If no download happened yet, click again here to download now.
|
||||
</a>
|
||||
@@ -503,7 +527,7 @@
|
||||
<button>
|
||||
<router-link
|
||||
:to="{ name: 'statistics' }"
|
||||
class="block w-fit text-center text-md 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.5 py-2 rounded-md mb-2"
|
||||
class="block w-fit text-center text-md 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-4 py-2 rounded-md mb-2"
|
||||
>
|
||||
See Global Animated History of Giving
|
||||
</router-link>
|
||||
@@ -950,13 +974,13 @@ export default class AccountViewView extends Vue {
|
||||
|
||||
public computedStartDownloadLinkClassNames() {
|
||||
return {
|
||||
invisible: this.downloadUrl,
|
||||
hidden: this.downloadUrl,
|
||||
};
|
||||
}
|
||||
|
||||
public computedDownloadLinkClassNames() {
|
||||
return {
|
||||
invisible: !this.downloadUrl,
|
||||
hidden: !this.downloadUrl,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -30,17 +30,19 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<input
|
||||
type="submit"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md mb-2"
|
||||
value="Add Contact"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<input
|
||||
type="submit"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md"
|
||||
value="Add Contact"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -119,7 +119,7 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||
import {
|
||||
AgreeVerifiableCredential,
|
||||
GiveServerRecord,
|
||||
GiveSummaryRecord,
|
||||
GiveVerifiableCredential,
|
||||
SCHEMA_ORG_CONTEXT,
|
||||
} from "@/libs/endorserServer";
|
||||
@@ -131,7 +131,7 @@ export default class ContactAmountssView extends Vue {
|
||||
activeDid = "";
|
||||
apiServer = "";
|
||||
contact: Contact | null = null;
|
||||
giveRecords: Array<GiveServerRecord> = [];
|
||||
giveRecords: Array<GiveSummaryRecord> = [];
|
||||
numAccounts = 0;
|
||||
|
||||
async beforeCreate() {
|
||||
@@ -197,7 +197,7 @@ export default class ContactAmountssView extends Vue {
|
||||
async loadGives(activeDid: string, contact: Contact) {
|
||||
try {
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
let result: Array<GiveServerRecord> = [];
|
||||
let result: Array<GiveSummaryRecord> = [];
|
||||
const url =
|
||||
this.apiServer +
|
||||
"/api/v2/report/gives?agentDid=" +
|
||||
@@ -252,7 +252,7 @@ export default class ContactAmountssView extends Vue {
|
||||
);
|
||||
}
|
||||
|
||||
const sortedResult: Array<GiveServerRecord> = R.sort(
|
||||
const sortedResult: Array<GiveSummaryRecord> = R.sort(
|
||||
(a, b) =>
|
||||
new Date(b.issuedAt).getTime() - new Date(a.issuedAt).getTime(),
|
||||
result,
|
||||
@@ -271,7 +271,7 @@ export default class ContactAmountssView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async confirm(record: GiveServerRecord) {
|
||||
async confirm(record: GiveSummaryRecord) {
|
||||
// Make claim
|
||||
// I use clone here because otherwise it gets a Proxy object.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@@ -65,17 +65,19 @@
|
||||
/>
|
||||
|
||||
<div class="mt-8">
|
||||
<input
|
||||
type="submit"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md mb-2"
|
||||
value="Look Up Contact"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<input
|
||||
type="submit"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md"
|
||||
value="Look Up Contact"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<span />
|
||||
<span>
|
||||
<a
|
||||
@click="showHintsForOnboarding()"
|
||||
href="/help-onboarding"
|
||||
target="_blank"
|
||||
class="text-xs uppercase 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-1.5 py-1 rounded-md ml-1"
|
||||
>
|
||||
Onboarding Guide
|
||||
@@ -302,7 +303,7 @@ import {
|
||||
import {
|
||||
CONTACT_CSV_HEADER,
|
||||
CONTACT_URL_PREFIX,
|
||||
GiveServerRecord,
|
||||
GiveSummaryRecord,
|
||||
GiveVerifiableCredential,
|
||||
isDid,
|
||||
RegisterVerifiableCredential,
|
||||
@@ -409,7 +410,7 @@ export default class ContactsView extends Vue {
|
||||
}
|
||||
|
||||
const handleResponse = (
|
||||
resp: { status: number; data: { data: GiveServerRecord[] } },
|
||||
resp: { status: number; data: { data: GiveSummaryRecord[] } },
|
||||
descriptions: Record<string, string>,
|
||||
confirmed: Record<string, number>,
|
||||
unconfirmed: Record<string, number>,
|
||||
@@ -510,18 +511,6 @@ export default class ContactsView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
showHintsForOnboarding() {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Onboard Someone",
|
||||
text: libsUtil.ONBOARD_MESSAGE,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
|
||||
async onClickNewContact(): Promise<void> {
|
||||
if (!this.contactInput) {
|
||||
this.$notify(
|
||||
@@ -531,7 +520,7 @@ export default class ContactsView extends Vue {
|
||||
title: "No Contact",
|
||||
text: "There was no contact info to add.",
|
||||
},
|
||||
-1,
|
||||
3000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -559,7 +548,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Contacts Added",
|
||||
text: "Each contact was added. Nothing was sent to the server.",
|
||||
},
|
||||
-1, // keeping it up so that the "visibility" message is seen
|
||||
3000, // keeping it up so that the "visibility" message is seen
|
||||
);
|
||||
} catch (e) {
|
||||
this.$notify(
|
||||
@@ -664,7 +653,7 @@ export default class ContactsView extends Vue {
|
||||
title: "No Contact Info",
|
||||
text: "The contact info could not be parsed.",
|
||||
},
|
||||
-1,
|
||||
3000,
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
@@ -686,7 +675,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Incomplete Contact",
|
||||
text: "Cannot add a contact without a DID.",
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -698,7 +687,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Invalid DID",
|
||||
text: "The DID is not valid. It must begin with 'did:'",
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -737,7 +726,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Contact Added",
|
||||
text: addedMessage,
|
||||
},
|
||||
-1, // keeping it up so that the "visibility" message is seen
|
||||
3000,
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -853,7 +842,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Registration Still Unknown",
|
||||
text: message,
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
} else if (resp.data?.success?.handleId) {
|
||||
contact.registered = true;
|
||||
@@ -892,7 +881,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Registration Error",
|
||||
text: userMessage,
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -933,7 +922,7 @@ export default class ContactsView extends Vue {
|
||||
(visibility ? "" : "not ") +
|
||||
"see your activity.",
|
||||
},
|
||||
-1,
|
||||
3000,
|
||||
);
|
||||
}
|
||||
contact.seesMe = visibility;
|
||||
@@ -953,7 +942,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Error Setting Visibility",
|
||||
text: message,
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -965,7 +954,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Error Setting Visibility",
|
||||
text: "Check connectivity and try again.",
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -997,7 +986,7 @@ export default class ContactsView extends Vue {
|
||||
(visibility ? "" : "not ") +
|
||||
"see your activity.",
|
||||
},
|
||||
-1,
|
||||
3000,
|
||||
);
|
||||
} else {
|
||||
console.error("Got bad server response checking visibility:", resp);
|
||||
@@ -1009,7 +998,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Error Checking Visibility",
|
||||
text: message,
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -1021,7 +1010,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Error Checking Visibility",
|
||||
text: "Check connectivity and try again.",
|
||||
},
|
||||
-1,
|
||||
3000,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1069,7 +1058,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Input Error",
|
||||
text: "This is not a valid number of hours: " + this.hourInput,
|
||||
},
|
||||
-1,
|
||||
3000,
|
||||
);
|
||||
} else if (parseFloat(this.hourInput) == 0 && !this.hourDescriptionInput) {
|
||||
this.$notify(
|
||||
@@ -1079,7 +1068,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Input Error",
|
||||
text: "Giving no hours or description does nothing.",
|
||||
},
|
||||
-1,
|
||||
3000,
|
||||
);
|
||||
} else if (!identity) {
|
||||
this.$notify(
|
||||
@@ -1089,7 +1078,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Status Error",
|
||||
text: "No identifier is available.",
|
||||
},
|
||||
-1,
|
||||
3000,
|
||||
);
|
||||
} else {
|
||||
// ask to confirm amount
|
||||
@@ -1218,7 +1207,7 @@ export default class ContactsView extends Vue {
|
||||
title: "Error Sending Give",
|
||||
text: userMessage,
|
||||
},
|
||||
-1,
|
||||
5000,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,19 +92,26 @@
|
||||
|
||||
<p class="text-center mb-2 mt-6 italic">
|
||||
Sign & Send to publish to the world
|
||||
<fa
|
||||
icon="circle-info"
|
||||
class="pl-2 text-blue-500 cursor-pointer"
|
||||
@click="explainData()"
|
||||
/>
|
||||
</p>
|
||||
<button
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md mb-2"
|
||||
@click="confirm"
|
||||
>
|
||||
Sign & Send
|
||||
</button>
|
||||
<button
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
@click="cancel"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<button
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md"
|
||||
@click="confirm"
|
||||
>
|
||||
Sign & Send
|
||||
</button>
|
||||
<button
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
@click="cancel"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -288,6 +295,45 @@ export default class GiftedDetails extends Vue {
|
||||
}
|
||||
|
||||
async confirm() {
|
||||
if (!this.activeDid) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "You must select an identifier before you can record a give.",
|
||||
},
|
||||
2000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (parseFloat(this.amountInput) < 0) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
text: "You may not send a negative number.",
|
||||
title: "",
|
||||
},
|
||||
2000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!this.description && !parseFloat(this.amountInput)) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: `You must enter a description or some number of ${
|
||||
this.libsUtil.UNIT_LONG[this.unitCode]
|
||||
}.`,
|
||||
},
|
||||
2000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -297,6 +343,7 @@ export default class GiftedDetails extends Vue {
|
||||
},
|
||||
1000,
|
||||
);
|
||||
|
||||
// this is asynchronous, but we don't need to wait for it to complete
|
||||
await this.recordGive();
|
||||
}
|
||||
@@ -309,34 +356,6 @@ export default class GiftedDetails extends Vue {
|
||||
* @param unitCode may be omitted, defaults to "HUR"
|
||||
*/
|
||||
public async recordGive() {
|
||||
if (!this.activeDid) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: "You must select an identifier before you can record a give.",
|
||||
},
|
||||
-1,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.description && !this.amountInput) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Error",
|
||||
text: `You must enter a description or some number of ${
|
||||
this.libsUtil.UNIT_LONG[this.unitCode]
|
||||
}.`,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const identity = await libsUtil.getIdentity(this.activeDid);
|
||||
const result = await createAndSubmitGive(
|
||||
@@ -424,5 +443,17 @@ export default class GiftedDetails extends Vue {
|
||||
result.response?.data?.error?.message
|
||||
);
|
||||
}
|
||||
|
||||
explainData() {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Data Sharing",
|
||||
text: libsUtil.PRIVACY_MESSAGE,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -301,12 +301,13 @@ import { sendTestThroughPushServer } from "@/libs/util";
|
||||
export default class HelpNotificationsView extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||
|
||||
subscription: PushSubscription | null = null;
|
||||
subscriptionJSON?: PushSubscriptionJSON;
|
||||
|
||||
async mounted() {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
this.subscription = await registration.pushManager.getSubscription();
|
||||
const fullSub = await registration.pushManager.getSubscription();
|
||||
this.subscriptionJSON = fullSub?.toJSON();
|
||||
} catch (error) {
|
||||
console.error("Mount error:", error);
|
||||
}
|
||||
@@ -315,13 +316,13 @@ export default class HelpNotificationsView extends Vue {
|
||||
alertWebPushSubscription() {
|
||||
console.log(
|
||||
"Web push subscription:",
|
||||
JSON.parse(JSON.stringify(this.subscription)), // gives more info than plain console logging
|
||||
JSON.parse(JSON.stringify(this.subscriptionJSON)), // gives more info than plain console logging
|
||||
);
|
||||
alert(JSON.stringify(this.subscription));
|
||||
alert(JSON.stringify(this.subscriptionJSON));
|
||||
}
|
||||
|
||||
async sendTestWebPushMessage(skipFilter: boolean = false) {
|
||||
if (!this.subscription) {
|
||||
if (!this.subscriptionJSON) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
@@ -336,7 +337,7 @@ export default class HelpNotificationsView extends Vue {
|
||||
}
|
||||
|
||||
try {
|
||||
await sendTestThroughPushServer(this.subscription, skipFilter);
|
||||
await sendTestThroughPushServer(this.subscriptionJSON, skipFilter);
|
||||
|
||||
this.$notify(
|
||||
{
|
||||
|
||||
69
src/views/HelpOnboardingView.vue
Normal file
69
src/views/HelpOnboardingView.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<!-- Don't include nav buttons since this is shown in a different window. -->
|
||||
|
||||
<!-- CONTENT -->
|
||||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||
<!-- Breadcrumb -->
|
||||
<div class="mb-8">
|
||||
<!-- Don't include 'back' button since this is shown in a different window. -->
|
||||
<!-- Heading -->
|
||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||
Time Safari Onboarding Instructions
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- eslint-disable prettier/prettier -->
|
||||
<div class="ml-4">
|
||||
<h1 class="font-bold text-xl">Install</h1>
|
||||
<div>
|
||||
<p>
|
||||
1) Have them visit TimeSafari.app in a browser, preferably Chrome or Safari.
|
||||
</p>
|
||||
<p>
|
||||
2) Have them "Install" the site to their desktop.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h1 class="font-bold text-xl">Add Contact & Register</h1>
|
||||
<div>
|
||||
<p>
|
||||
3) Have them follow their yellow prompts.
|
||||
</p>
|
||||
<p>
|
||||
4) Add them to your contacts <fa icon="users" />
|
||||
</p>
|
||||
<p>
|
||||
5) Register them <fa icon="person-circle-question" />
|
||||
</p>
|
||||
<p>
|
||||
6) Add yourself to their contacts <fa icon="users" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h1 class="font-bold text-xl">Enable Notifications</h1>
|
||||
<div>
|
||||
<p>
|
||||
7) Enable notifications from <fa icon="circle-user" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h1 class="font-bold text-xl">Discuss Backups</h1>
|
||||
<div>
|
||||
<p>
|
||||
8) Exporting backups <fa icon="circle-user" /> are important if they lose their phone --- especially for the Identifier Seed!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- eslint enable -->
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
|
||||
@Component({ components: { QuickNav } })
|
||||
export default class Help extends Vue {}
|
||||
</script>
|
||||
@@ -39,22 +39,22 @@
|
||||
and network.
|
||||
</p>
|
||||
<p>
|
||||
You can show giving and also offer help to ideas, based on others'
|
||||
willingness to help out, too. You can record your own ideas and invite
|
||||
others to collaborate.
|
||||
You highlight giving and also offer help to ideas -- which could be
|
||||
conditional on others' willingness to help, too.
|
||||
You can record your own ideas and invite others to collaborate.
|
||||
</p>
|
||||
<p>
|
||||
This app uses the power of cryptography to build a reputation, recording
|
||||
activity that you can share at your discretion. You put some activity
|
||||
public, but your sensitive information is not shared with anyone,
|
||||
including our services. This is in contrast to Meta and Google, who hold
|
||||
your data and allow you use it. Those services are useful, but they have
|
||||
the control; this app gives you the control.
|
||||
public, but these services don't share your ID with others without explicit consent.
|
||||
This is in contrast to Meta and Google, who hold
|
||||
your data and allow you use it while they manage sharing...
|
||||
those services are useful but they have the control, whereas this app gives you the control.
|
||||
</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">How do I get started?</h2>
|
||||
<p>
|
||||
You need someone to register you -- usually the person who told you
|
||||
You need someone to register you, like the person who told you
|
||||
about this app, on the Contacts
|
||||
<fa icon="users" class="fa-fw" /> page. After they register you, you can
|
||||
select any contact on the home page (or "anonymous") and record your
|
||||
@@ -83,9 +83,9 @@
|
||||
|
||||
<h2 class="text-xl font-semibold">How do I add someone else?</h2>
|
||||
<p>
|
||||
<button class="text-blue-500" @click="showOnboardInfo">
|
||||
<a href="/help-onboarding" target="_blank" class="text-blue-500">
|
||||
Click here to show an alert with the steps.
|
||||
</button>
|
||||
</a>
|
||||
To start scanning, go
|
||||
<router-link class="text-blue-500" to="/contact-qr">here.</router-link>
|
||||
</p>
|
||||
@@ -198,9 +198,7 @@
|
||||
<ul>
|
||||
<li class="list-disc list-outside ml-4">
|
||||
Chrome:
|
||||
<a href="chrome://settings/content/all" class="text-blue-500"
|
||||
>clear here</a
|
||||
>
|
||||
Clear at chrome://settings/content/all and
|
||||
also clear under dev tools Application
|
||||
</li>
|
||||
<li class="list-disc list-outside ml-4">
|
||||
@@ -378,7 +376,6 @@ import { Component, Vue } from "vue-facing-decorator";
|
||||
import * as Package from "../../package.json";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import { NotificationIface } from "@/constants/app";
|
||||
import { ONBOARD_MESSAGE } from "@/libs/util";
|
||||
|
||||
@Component({ components: { QuickNav } })
|
||||
export default class Help extends Vue {
|
||||
@@ -386,17 +383,5 @@ export default class Help extends Vue {
|
||||
|
||||
package = Package;
|
||||
commitHash = import.meta.env.VITE_GIT_HASH;
|
||||
|
||||
showOnboardInfo() {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "info",
|
||||
title: "Onboard Someone",
|
||||
text: ONBOARD_MESSAGE,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -118,7 +118,9 @@
|
||||
<h2 class="text-xl font-bold">Record Something Given By:</h2>
|
||||
</div>
|
||||
|
||||
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
||||
<ul
|
||||
class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-3 gap-y-5 text-center mb-5"
|
||||
>
|
||||
<li @click="openDialog()">
|
||||
<img
|
||||
src="../assets/blank-square.svg"
|
||||
@@ -158,7 +160,7 @@
|
||||
</router-link>
|
||||
<button
|
||||
@click="openGiftedPrompts()"
|
||||
class="block text-center text-md font-bold 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-2 py-3 rounded-md"
|
||||
class="block text-center text-md 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-4 py-2 rounded-md"
|
||||
>
|
||||
Ideas...
|
||||
</button>
|
||||
@@ -172,10 +174,29 @@
|
||||
showGivenToUser="true"
|
||||
/>
|
||||
<GiftedPrompts ref="giftedPrompts" />
|
||||
<FeedFilters ref="feedFilters" />
|
||||
|
||||
<!-- Results List -->
|
||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
||||
<h2 class="text-xl font-bold mb-4">Latest Activity</h2>
|
||||
<div class="flex items-center mb-4">
|
||||
<h2 class="text-xl font-bold">Latest Activity</h2>
|
||||
<button @click="openFeedFilters()" class="block text-center ml-auto">
|
||||
<span class="text-sm uppercase text-white">
|
||||
<span
|
||||
v-if="resultsAreFiltered()"
|
||||
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"
|
||||
>
|
||||
Filtered
|
||||
</span>
|
||||
<span
|
||||
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"
|
||||
>
|
||||
Unfiltered
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<InfiniteScroll @reached-bottom="loadMoreGives">
|
||||
<ul class="border-t border-slate-300">
|
||||
<li
|
||||
@@ -191,37 +212,36 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-12">
|
||||
<span class="col-span-11 justify-self-start">
|
||||
<span class="col-span-1 justify-self-start">
|
||||
<span>
|
||||
<fa
|
||||
v-if="record.giver.known || record.receiver.known"
|
||||
icon="circle-user"
|
||||
class="col-span-1 pt-1 pl-0 pr-3 text-slate-500"
|
||||
/>
|
||||
<fa
|
||||
v-else
|
||||
icon="gift"
|
||||
class="col-span-1 pt-1 pl-3 pr-0 text-slate-500"
|
||||
class="pt-1 text-slate-500"
|
||||
/>
|
||||
<fa v-else icon="gift" class="pt-1 pl-3 text-slate-500" />
|
||||
</span>
|
||||
</span>
|
||||
<span class="col-span-10 justify-self-stretch">
|
||||
<span class="pl-2">
|
||||
{{ giveDescription(record) }}
|
||||
</span>
|
||||
{{ giveDescription(record) }}
|
||||
<a @click="onClickLoadClaim(record.jwtId)">
|
||||
<fa
|
||||
icon="circle-info"
|
||||
icon="file-lines"
|
||||
class="pl-2 text-blue-500 cursor-pointer"
|
||||
></fa>
|
||||
</a>
|
||||
</span>
|
||||
<span class="col-span-1 justify-self-end shrink">
|
||||
<span class="col-span-1 justify-self-end">
|
||||
<router-link
|
||||
v-if="record.fulfillsPlanHandleId"
|
||||
:to="
|
||||
'/project/' +
|
||||
encodeURIComponent(record.fulfillsPlanHandleId)
|
||||
"
|
||||
class="justify-end"
|
||||
>
|
||||
<fa icon="hammer" class="ml-4 pl-2 text-blue-500"></fa>
|
||||
<fa icon="hammer" class="text-blue-500"></fa>
|
||||
</router-link>
|
||||
</span>
|
||||
</div>
|
||||
@@ -238,11 +258,17 @@
|
||||
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading…
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="!isFeedLoading && feedData.length === 0">
|
||||
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
||||
No claims match your filters.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import * as R from "ramda";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
@@ -250,6 +276,7 @@ import { Component, Vue } from "vue-facing-decorator";
|
||||
import EntityIcon from "@/components/EntityIcon.vue";
|
||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||
import GiftedPrompts from "@/components/GiftedPrompts.vue";
|
||||
import FeedFilters from "@/components/FeedFilters.vue";
|
||||
import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import TopMessage from "@/components/TopMessage.vue";
|
||||
@@ -257,22 +284,30 @@ import { NotificationIface } from "@/constants/app";
|
||||
import { db, accountsDB } from "@/db/index";
|
||||
import { Account } from "@/db/tables/accounts";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||
import {
|
||||
BoundingBox,
|
||||
isAnyFeedFilterOn,
|
||||
MASTER_SETTINGS_KEY,
|
||||
Settings,
|
||||
} from "@/db/tables/settings";
|
||||
import { accessToken } from "@/libs/crypto";
|
||||
import {
|
||||
contactForDid,
|
||||
containsNonHiddenDid,
|
||||
didInfoForContact,
|
||||
getPlanFromCache,
|
||||
GiverInputInfo,
|
||||
GiveServerRecord,
|
||||
GiveSummaryRecord,
|
||||
} from "@/libs/endorserServer";
|
||||
import { generateSaveAndActivateIdentity } from "@/libs/util";
|
||||
|
||||
interface GiveRecordWithContactInfo extends GiveServerRecord {
|
||||
interface GiveRecordWithContactInfo extends GiveSummaryRecord {
|
||||
giver: {
|
||||
displayName: string;
|
||||
known: boolean;
|
||||
};
|
||||
image: string;
|
||||
recipientProjectName: string | undefined;
|
||||
receiver: {
|
||||
displayName: string;
|
||||
known: boolean;
|
||||
@@ -283,6 +318,7 @@ interface GiveRecordWithContactInfo extends GiveServerRecord {
|
||||
components: {
|
||||
GiftedDialog,
|
||||
GiftedPrompts,
|
||||
FeedFilters,
|
||||
QuickNav,
|
||||
EntityIcon,
|
||||
InfiniteScroll,
|
||||
@@ -299,9 +335,16 @@ export default class HomeView extends Vue {
|
||||
feedData: GiveRecordWithContactInfo[] = [];
|
||||
feedPreviousOldestId?: string;
|
||||
feedLastViewedClaimId?: string;
|
||||
isAnyFeedFilterOn: boolean;
|
||||
isCreatingIdentifier = false;
|
||||
isFeedFilteredByVisible = false;
|
||||
isFeedFilteredByNearby = false;
|
||||
isFeedLoading = true;
|
||||
isRegistered = false;
|
||||
searchBoxes: Array<{
|
||||
name: string;
|
||||
bbox: BoundingBox;
|
||||
}> = [];
|
||||
showShortcutBvc = false;
|
||||
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
|
||||
|
||||
@@ -324,7 +367,7 @@ export default class HomeView extends Vue {
|
||||
return headers;
|
||||
}
|
||||
|
||||
async created() {
|
||||
async mounted() {
|
||||
try {
|
||||
await accountsDB.open();
|
||||
const allAccounts = await accountsDB.accounts.toArray();
|
||||
@@ -336,9 +379,14 @@ export default class HomeView extends Vue {
|
||||
this.activeDid = settings?.activeDid || "";
|
||||
this.allContacts = await db.contacts.toArray();
|
||||
this.feedLastViewedClaimId = settings?.lastViewedClaimId;
|
||||
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
|
||||
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
|
||||
this.isRegistered = !!settings?.isRegistered;
|
||||
this.searchBoxes = settings?.searchBoxes || [];
|
||||
this.showShortcutBvc = !!settings?.showShortcutBvc;
|
||||
|
||||
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
|
||||
|
||||
if (this.allMyDids.length === 0) {
|
||||
this.isCreatingIdentifier = true;
|
||||
this.activeDid = await generateSaveAndActivateIdentity();
|
||||
@@ -367,6 +415,10 @@ export default class HomeView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
resultsAreFiltered() {
|
||||
return this.isFeedFilteredByVisible || this.isFeedFilteredByNearby;
|
||||
}
|
||||
|
||||
notificationsSupported() {
|
||||
return "Notification" in window;
|
||||
}
|
||||
@@ -376,27 +428,34 @@ export default class HomeView extends Vue {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
if (this.activeDid) {
|
||||
await accountsDB.open();
|
||||
const allAccounts = await accountsDB.accounts.toArray();
|
||||
const account = allAccounts.find(
|
||||
(acc) => acc.did === this.activeDid,
|
||||
) as Account;
|
||||
const identity = JSON.parse(account?.identity || "null");
|
||||
|
||||
if (!identity) {
|
||||
if (identity) {
|
||||
headers["Authorization"] = "Bearer " + (await accessToken(identity));
|
||||
} else {
|
||||
throw new Error(
|
||||
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service. Switch your ID.",
|
||||
);
|
||||
}
|
||||
|
||||
headers["Authorization"] = "Bearer " + (await accessToken(identity));
|
||||
} else {
|
||||
// it's OK without auth... we just won't get any identifiers
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
// only called when a setting was changed
|
||||
async reloadFeedOnChange() {
|
||||
await db.open();
|
||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
||||
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
|
||||
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
|
||||
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
|
||||
|
||||
this.feedData = [];
|
||||
this.feedPreviousOldestId = undefined;
|
||||
this.updateAllFeed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Data loader used by infinite scroller
|
||||
* @param payload is the flag from the InfiniteScroll indicating if it should load
|
||||
@@ -407,14 +466,30 @@ export default class HomeView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
latLongInAnySearchBox(lat: number, long: number) {
|
||||
for (const boxInfo of this.searchBoxes) {
|
||||
if (
|
||||
boxInfo.bbox.westLong <= long &&
|
||||
long <= boxInfo.bbox.eastLong &&
|
||||
boxInfo.bbox.minLat <= lat &&
|
||||
lat <= boxInfo.bbox.maxLat
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async updateAllFeed() {
|
||||
this.isFeedLoading = true;
|
||||
let endOfResults = true;
|
||||
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
|
||||
.then(async (results) => {
|
||||
if (results.data.length > 0) {
|
||||
endOfResults = false;
|
||||
// include the descriptions of the giver and receiver
|
||||
const newFeedData: GiveRecordWithContactInfo = results.data.map(
|
||||
(record: GiveServerRecord) => {
|
||||
const identity = await this.getIdentity(this.activeDid);
|
||||
const newFeedData: Array<Promise<GiveRecordWithContactInfo>> =
|
||||
results.data.map(async (record: GiveSummaryRecord) => {
|
||||
// similar code is in endorser-mobile utility.ts
|
||||
// claim.claim happen for some claims wrapped in a Verifiable Credential
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -425,6 +500,36 @@ export default class HomeView extends Vue {
|
||||
// recipient.did is for legacy data, before March 2023
|
||||
const recipientDid =
|
||||
claim.recipient?.identifier || (claim.recipient as any)?.did; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
const plan = await getPlanFromCache(
|
||||
record.fulfillsPlanHandleId,
|
||||
identity,
|
||||
this.axios,
|
||||
this.apiServer,
|
||||
);
|
||||
|
||||
// check if the record should be filtered out
|
||||
let anyMatch = false;
|
||||
if (
|
||||
this.isFeedFilteredByVisible &&
|
||||
containsNonHiddenDid(record)
|
||||
) {
|
||||
// has a visible DID so it's a keeper
|
||||
anyMatch = true;
|
||||
}
|
||||
if (!anyMatch && this.isFeedFilteredByNearby) {
|
||||
// check if the associated project has a location inside user's search box
|
||||
if (record.fulfillsPlanHandleId) {
|
||||
if (plan?.locLat && plan?.locLon) {
|
||||
if (this.latLongInAnySearchBox(plan.locLat, plan.locLon)) {
|
||||
anyMatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.isAnyFeedFilterOn && !anyMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...record,
|
||||
giver: didInfoForContact(
|
||||
@@ -434,6 +539,7 @@ export default class HomeView extends Vue {
|
||||
this.allMyDids,
|
||||
),
|
||||
image: claim.image,
|
||||
recipientProjectName: plan?.name,
|
||||
receiver: didInfoForContact(
|
||||
recipientDid,
|
||||
this.activeDid,
|
||||
@@ -441,9 +547,11 @@ export default class HomeView extends Vue {
|
||||
this.allMyDids,
|
||||
),
|
||||
};
|
||||
},
|
||||
);
|
||||
this.feedData = this.feedData.concat(newFeedData);
|
||||
});
|
||||
const allNewFeedData: GiveRecordWithContactInfo[] =
|
||||
await Promise.all(newFeedData);
|
||||
const filteredFeedData = allNewFeedData.filter(R.isNotNil);
|
||||
this.feedData = this.feedData.concat(filteredFeedData);
|
||||
this.feedPreviousOldestId =
|
||||
results.data[results.data.length - 1].jwtId;
|
||||
// The following update is only done on the first load.
|
||||
@@ -470,6 +578,10 @@ export default class HomeView extends Vue {
|
||||
-1,
|
||||
);
|
||||
});
|
||||
if (this.feedData.length === 0 && !endOfResults) {
|
||||
// repeat until there's at least some data
|
||||
this.updateAllFeed();
|
||||
}
|
||||
this.isFeedLoading = false;
|
||||
}
|
||||
|
||||
@@ -536,12 +648,28 @@ export default class HomeView extends Vue {
|
||||
return `${giverInfo.displayName} gave to ${recipientInfo.displayName}: ${gaveAmount}`;
|
||||
} else if (giverInfo.known) {
|
||||
// giver is named but recipient is not
|
||||
|
||||
// show the project name if to one
|
||||
if (giveRecord.recipientProjectName) {
|
||||
// retrieve the project name
|
||||
return `${giverInfo.displayName} gave: ${gaveAmount} (to the project ${giveRecord.recipientProjectName})`;
|
||||
}
|
||||
|
||||
// it's not to a project
|
||||
return `${giverInfo.displayName} gave: ${gaveAmount} (to ${recipientInfo.displayName})`;
|
||||
} else if (recipientInfo.known) {
|
||||
// recipient is named but giver is not
|
||||
return `${recipientInfo.displayName} received: ${gaveAmount} (from ${giverInfo.displayName})`;
|
||||
} else {
|
||||
// neither giver nor recipient are named
|
||||
|
||||
// show the project name if to one
|
||||
if (giveRecord.recipientProjectName) {
|
||||
// retrieve the project name
|
||||
return `${gaveAmount} (to the project ${giveRecord.recipientProjectName})`;
|
||||
}
|
||||
|
||||
// it's not to a project
|
||||
let peopleInfo;
|
||||
if (giverInfo.displayName === recipientInfo.displayName) {
|
||||
peopleInfo = `between two who are ${giverInfo.displayName}`;
|
||||
@@ -574,5 +702,9 @@ export default class HomeView extends Vue {
|
||||
openGiftedPrompts() {
|
||||
(this.$refs.giftedPrompts as GiftedPrompts).open();
|
||||
}
|
||||
|
||||
openFeedFilters() {
|
||||
(this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -56,19 +56,21 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<button
|
||||
@click="fromMnemonic()"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md mb-2"
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<button
|
||||
@click="onCancelClick()"
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<button
|
||||
@click="fromMnemonic()"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md"
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<button
|
||||
@click="onCancelClick()"
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -49,19 +49,21 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
<button
|
||||
@click="incrementDerivation()"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md mb-2"
|
||||
>
|
||||
Increment and Import
|
||||
</button>
|
||||
<button
|
||||
@click="onCancelClick()"
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<button
|
||||
@click="incrementDerivation()"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md"
|
||||
>
|
||||
Increment and Import
|
||||
</button>
|
||||
<button
|
||||
@click="onCancelClick()"
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -22,21 +22,23 @@
|
||||
/>
|
||||
|
||||
<div class="mt-8">
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md mb-2"
|
||||
@click="onClickSaveChanges()"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
<!-- SHOW ME instead while processing saving changes -->
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
@click="onClickCancel()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md mb-2"
|
||||
@click="onClickSaveChanges()"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
<!-- SHOW ME instead while processing saving changes -->
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
@click="onClickCancel()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -73,16 +73,17 @@
|
||||
/>
|
||||
<label for="includeLocation">Include Location</label>
|
||||
</div>
|
||||
<div v-if="includeLocation" style="height: 600px; width: 800px">
|
||||
<div class="px-2 py-2">
|
||||
<div v-if="includeLocation" class="mb-4 aspect-video">
|
||||
<p class="text-sm mb-2 text-slate-500">
|
||||
For your security, choose a location nearby but not exactly at the
|
||||
place.
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<l-map
|
||||
ref="map"
|
||||
v-model:zoom="zoom"
|
||||
:center="[0, 0]"
|
||||
class="!z-40 rounded-md"
|
||||
@click="
|
||||
(event) => {
|
||||
latitude = event.latlng.lat;
|
||||
@@ -104,28 +105,30 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<button
|
||||
:disabled="isHiddenSave"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md mb-2"
|
||||
@click="onSaveProjectClick()"
|
||||
>
|
||||
<!-- SHOW if in idle state -->
|
||||
<span :class="{ hidden: isHiddenSave }">Save Project</span>
|
||||
|
||||
<!-- SHOW if in saving state; DISABLE button while in saving state -->
|
||||
<span :class="{ hidden: isHiddenSpinner }">
|
||||
<!-- icon no worky? -->
|
||||
<i class="fa-solid fa-spinner fa-spin-pulse"></i>
|
||||
Saving...</span
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<button
|
||||
:disabled="isHiddenSave"
|
||||
class="block w-full text-center text-lg font-bold uppercase 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-3 rounded-md mb-2"
|
||||
@click="onSaveProjectClick()"
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
@click="onCancelClick()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<!-- SHOW if in idle state -->
|
||||
<span :class="{ hidden: isHiddenSave }">Save Project</span>
|
||||
|
||||
<!-- SHOW if in saving state; DISABLE button while in saving state -->
|
||||
<span :class="{ hidden: isHiddenSpinner }">
|
||||
<!-- icon no worky? -->
|
||||
<i class="fa-solid fa-spinner fa-spin-pulse"></i>
|
||||
Saving...</span
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
@click="onCancelClick()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -265,6 +268,8 @@ export default class NewEditProjectView extends Vue {
|
||||
vcClaim.agent = {
|
||||
identifier: this.agentDid,
|
||||
};
|
||||
} else {
|
||||
delete vcClaim.agent;
|
||||
}
|
||||
if (this.includeLocation) {
|
||||
vcClaim.location = {
|
||||
@@ -274,6 +279,8 @@ export default class NewEditProjectView extends Vue {
|
||||
longitude: this.longitude,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
delete vcClaim.location;
|
||||
}
|
||||
// Make a payload for the claim
|
||||
const vcPayload = {
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
</div>
|
||||
|
||||
<a @click="onClickLoadClaim(projectId)" class="cursor-pointer">
|
||||
<fa icon="circle-info" class="pl-2 pt-1 text-blue-500" />
|
||||
<fa icon="file-lines" class="pl-2 pt-1 text-blue-500" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -134,7 +134,9 @@
|
||||
<div class="text-center">
|
||||
<p class="mt-2 mb-4 text-center">Record a contribution from:</p>
|
||||
</div>
|
||||
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
||||
<ul
|
||||
class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-x-3 gap-y-5 text-center mb-5"
|
||||
>
|
||||
<li @click="openGiftDialog({ name: 'you', did: activeDid })">
|
||||
<fa icon="hand" class="fa-fw text-slate-400 text-5xl" />
|
||||
<h3
|
||||
@@ -230,7 +232,7 @@
|
||||
@click="onClickLoadClaim(offer.jwtId as string)"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<fa icon="circle-info" class="pl-2 pt-1 text-blue-500" />
|
||||
<fa icon="file-lines" class="pl-2 pt-1 text-blue-500" />
|
||||
</a>
|
||||
<a
|
||||
v-if="checkIsFulfillable(offer)"
|
||||
@@ -289,7 +291,7 @@
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<a @click="onClickLoadClaim(give.jwtId)">
|
||||
<fa icon="circle-info" class="text-blue-500 cursor-pointer" />
|
||||
<fa icon="file-lines" class="text-blue-500 cursor-pointer" />
|
||||
</a>
|
||||
<a v-if="checkIsConfirmable(give)" @click="confirmClaim(give)">
|
||||
<fa icon="circle-check" class="text-blue-500 cursor-pointer" />
|
||||
@@ -360,11 +362,11 @@ import { accessToken } from "@/libs/crypto";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
import {
|
||||
BLANK_GENERIC_SERVER_RECORD,
|
||||
GenericServerRecord,
|
||||
GenericCredWrapper,
|
||||
GiverInputInfo,
|
||||
GiveServerRecord,
|
||||
OfferServerRecord,
|
||||
PlanServerRecord,
|
||||
GiveSummaryRecord,
|
||||
OfferSummaryRecord,
|
||||
PlanSummaryRecord,
|
||||
} from "@/libs/endorserServer";
|
||||
import * as serverUtil from "@/libs/endorserServer";
|
||||
|
||||
@@ -388,14 +390,14 @@ export default class ProjectViewView extends Vue {
|
||||
apiServer = "";
|
||||
description = "";
|
||||
expanded = false;
|
||||
fulfilledByThis: PlanServerRecord | null = null;
|
||||
fulfillersToThis: Array<PlanServerRecord> = [];
|
||||
givesToThis: Array<GiveServerRecord> = [];
|
||||
fulfilledByThis: PlanSummaryRecord | null = null;
|
||||
fulfillersToThis: Array<PlanSummaryRecord> = [];
|
||||
givesToThis: Array<GiveSummaryRecord> = [];
|
||||
issuer = "";
|
||||
latitude = 0;
|
||||
longitude = 0;
|
||||
name = "";
|
||||
offersToThis: Array<OfferServerRecord> = [];
|
||||
offersToThis: Array<OfferSummaryRecord> = [];
|
||||
projectId = localStorage.getItem("projectId") || ""; // handle ID
|
||||
showDidCopy = false;
|
||||
timeSince = "";
|
||||
@@ -718,8 +720,8 @@ export default class ProjectViewView extends Vue {
|
||||
this.$router.push(route);
|
||||
}
|
||||
|
||||
checkIsFulfillable(offer: OfferServerRecord) {
|
||||
const offerRecord: GenericServerRecord = {
|
||||
checkIsFulfillable(offer: OfferSummaryRecord) {
|
||||
const offerRecord: GenericCredWrapper = {
|
||||
...BLANK_GENERIC_SERVER_RECORD,
|
||||
claim: offer.fullClaim,
|
||||
claimType: "Offer",
|
||||
@@ -728,8 +730,8 @@ export default class ProjectViewView extends Vue {
|
||||
return libsUtil.canFulfillOffer(offerRecord);
|
||||
}
|
||||
|
||||
onClickFulfillGiveToOffer(offer: OfferServerRecord) {
|
||||
const offerRecord: GenericServerRecord = {
|
||||
onClickFulfillGiveToOffer(offer: OfferSummaryRecord) {
|
||||
const offerRecord: GenericCredWrapper = {
|
||||
...BLANK_GENERIC_SERVER_RECORD,
|
||||
claim: offer.fullClaim,
|
||||
issuer: offer.offeredByDid,
|
||||
@@ -768,8 +770,8 @@ export default class ProjectViewView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
checkIsConfirmable(give: GiveServerRecord) {
|
||||
const giveDetails: GenericServerRecord = {
|
||||
checkIsConfirmable(give: GiveSummaryRecord) {
|
||||
const giveDetails: GenericCredWrapper = {
|
||||
...BLANK_GENERIC_SERVER_RECORD,
|
||||
claim: give.fullClaim,
|
||||
claimType: "GiveAction",
|
||||
@@ -779,7 +781,7 @@ export default class ProjectViewView extends Vue {
|
||||
}
|
||||
|
||||
// similar code is found in ClaimView
|
||||
async confirmClaim(give: GiveServerRecord) {
|
||||
async confirmClaim(give: GiveSummaryRecord) {
|
||||
if (confirm("Do you personally confirm that this is true?")) {
|
||||
// similar logic is found in endorser-mobile
|
||||
const goodClaim = serverUtil.removeSchemaContext(
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
|
||||
<a @click="onClickLoadClaim(offer.jwtId)">
|
||||
<fa
|
||||
icon="circle-info"
|
||||
icon="file-lines"
|
||||
class="pl-2 text-blue-500 cursor-pointer"
|
||||
></fa>
|
||||
</a>
|
||||
@@ -223,7 +223,7 @@ import InfiniteScroll from "@/components/InfiniteScroll.vue";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import ProjectIcon from "@/components/ProjectIcon.vue";
|
||||
import TopMessage from "@/components/TopMessage.vue";
|
||||
import { OfferServerRecord, PlanData } from "@/libs/endorserServer";
|
||||
import { OfferSummaryRecord, PlanData } from "@/libs/endorserServer";
|
||||
import EntityIcon from "@/components/EntityIcon.vue";
|
||||
|
||||
@Component({
|
||||
@@ -237,7 +237,7 @@ export default class ProjectsView extends Vue {
|
||||
currentIid: IIdentifier;
|
||||
isLoading = false;
|
||||
numAccounts = 0;
|
||||
offers: OfferServerRecord[] = [];
|
||||
offers: OfferSummaryRecord[] = [];
|
||||
showOffers = true;
|
||||
showProjects = false;
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
}}
|
||||
<a @click="onClickLoadClaim(record.id)">
|
||||
<fa
|
||||
icon="circle-info"
|
||||
icon="file-lines"
|
||||
class="pl-2 text-blue-500 cursor-pointer"
|
||||
/>
|
||||
</a>
|
||||
@@ -146,7 +146,7 @@ import {
|
||||
createAndSubmitConfirmation,
|
||||
createAndSubmitGive,
|
||||
ErrorResult,
|
||||
GenericServerRecord,
|
||||
GenericCredWrapper,
|
||||
GenericVerifiableCredential,
|
||||
} from "@/libs/endorserServer";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
@@ -166,7 +166,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
allMyDids: Array<string> = [];
|
||||
apiServer = "";
|
||||
claimCountWithHidden = 0;
|
||||
claimsToConfirm: GenericServerRecord[] = [];
|
||||
claimsToConfirm: GenericCredWrapper[] = [];
|
||||
claimsToConfirmSelected: string[] = [];
|
||||
description = "breakfast";
|
||||
loadingConfirms = true;
|
||||
@@ -228,7 +228,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
}
|
||||
await response.json().then((data) => {
|
||||
const dataByOthers = R.reject(
|
||||
(claim: GenericServerRecord) => claim.issuer === this.activeDid,
|
||||
(claim: GenericCredWrapper) => claim.issuer === this.activeDid,
|
||||
data,
|
||||
);
|
||||
const dataByOthersWithoutHidden = R.reject(
|
||||
|
||||
@@ -64,10 +64,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="height: 600px; width: 800px">
|
||||
<div class="mb-4 aspect-video">
|
||||
<l-map
|
||||
ref="map"
|
||||
:center="[localCenterLat, localCenterLong]"
|
||||
class="!z-40 rounded-md"
|
||||
v-model:zoom="localZoom"
|
||||
@click="setMapPoint"
|
||||
>
|
||||
@@ -208,9 +209,9 @@ export default class DiscoverView extends Vue {
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Saved",
|
||||
text: "That has been saved in your preferences.",
|
||||
text: "That has been saved in your preferences. You can now filter by it on your home screen feed.",
|
||||
},
|
||||
-1,
|
||||
7000,
|
||||
);
|
||||
this.$router.back();
|
||||
} catch (err) {
|
||||
@@ -246,6 +247,7 @@ export default class DiscoverView extends Vue {
|
||||
await db.open();
|
||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||
searchBoxes: [],
|
||||
filterFeedByNearby: false,
|
||||
});
|
||||
this.searchBox = null;
|
||||
this.localCenterLat = 0;
|
||||
|
||||
@@ -23,36 +23,40 @@
|
||||
|
||||
<!-- id used by puppeteer test script -->
|
||||
<div id="start-question" class="mt-8">
|
||||
<p class="text-center text-xl font-light">
|
||||
Do you want a new identifier of your own?
|
||||
</p>
|
||||
<p class="text-center font-light">
|
||||
If you haven't used this before, click "Yes" to generate a new
|
||||
identifier.
|
||||
</p>
|
||||
<p class="text-center mb-4 font-light">
|
||||
Only click "No" if you have a seed of 12 or 24 words generated
|
||||
elsewhere.
|
||||
</p>
|
||||
<a
|
||||
@click="onClickYes()"
|
||||
class="block w-full text-center text-lg uppercase 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-3 rounded-md"
|
||||
>
|
||||
Yes, generate one
|
||||
</a>
|
||||
<a
|
||||
@click="onClickNo()"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md mt-2"
|
||||
>
|
||||
No, I have a seed
|
||||
</a>
|
||||
<a
|
||||
v-if="numAccounts > 0"
|
||||
@click="onClickDerive()"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md mt-2"
|
||||
>
|
||||
Derive new address from existing seed
|
||||
</a>
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<p class="text-center text-xl font-light">
|
||||
Do you want a new identifier of your own?
|
||||
</p>
|
||||
<p class="text-center font-light">
|
||||
If you haven't used this before, click "Yes" to generate a new
|
||||
identifier.
|
||||
</p>
|
||||
<p class="text-center mb-4 font-light">
|
||||
Only click "No" if you have a seed of 12 or 24 words generated
|
||||
elsewhere.
|
||||
</p>
|
||||
<a
|
||||
@click="onClickYes()"
|
||||
class="block w-full text-center text-lg uppercase 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-3 rounded-md mb-2"
|
||||
>
|
||||
Yes, generate one
|
||||
</a>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||
<a
|
||||
@click="onClickNo()"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
No, I have a seed
|
||||
</a>
|
||||
<a
|
||||
v-if="numAccounts > 0"
|
||||
@click="onClickDerive()"
|
||||
class="block w-full text-center text-md 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.5 py-2 rounded-md"
|
||||
>
|
||||
Derive new address from existing seed
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user