Browse Source

automatically create an identity on the first page (and other UI tweaks)

kb/add-usage-guide
Trent Larson 10 months ago
parent
commit
d7ef07c2e2
  1. 2
      project.task.yaml
  2. 33
      src/libs/util.ts
  3. 120
      src/views/AccountViewView.vue
  4. 2
      src/views/ContactsView.vue
  5. 12
      src/views/HelpView.vue
  6. 53
      src/views/HomeView.vue
  7. 29
      src/views/ImportAccountView.vue
  8. 7
      src/views/NewEditProjectView.vue
  9. 27
      src/views/NewIdentifierView.vue

2
project.task.yaml

@ -2,7 +2,7 @@
tasks: tasks:
- create an identifier automatically, with a message that they can import a different one - create an identifier automatically, with a message that they can import a different one
- choose an agent - make a contact chooser - choose an agent via a contact chooser (not just copy-paste a DID)
- make set-name request yellow - make set-name request yellow
- make the "give" on contact screen work like other give (allowing donation vs current blank) - make the "give" on contact screen work like other give (allowing donation vs current blank)
- make give action executable right from an offer - make give action executable right from an offer

33
src/libs/util.ts

@ -3,8 +3,9 @@
import axios, { AxiosResponse } from "axios"; import axios, { AxiosResponse } from "axios";
import { DEFAULT_PUSH_SERVER } from "@/constants/app"; import { DEFAULT_PUSH_SERVER } from "@/constants/app";
import { db } from "@/db/index"; import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
// 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;
@ -18,6 +19,36 @@ export const isGlobalUri = (uri: string) => {
export const ONBOARD_MESSAGE = export const ONBOARD_MESSAGE =
"1) Check that they have entered their name on the profile page in their device. 2) Add them to your Contacts by scanning with the QR icon that is by the input box. 3) Click the person icon to register them. 4) Have them go to their Contact page and scan your QR to add you to their list."; "1) Check that they have entered their name on the profile page in their device. 2) Add them to your Contacts by scanning with the QR icon that is by the input box. 3) Click the person icon to register them. 4) Have them go to their Contact page and scan your QR to add you to their list.";
/**
* Generates a new identity, saves it to the database, and sets it as the active identity.
* @return {Promise<string>} with the DID of the new identity
*/
export const generateSaveAndActivateIdentity = async (): Promise<string> => {
const mnemonic = generateSeed();
// address is 0x... ETH address, without "did:eth:"
const [address, privateHex, publicHex, derivationPath] =
deriveAddress(mnemonic);
const newId = newIdentifier(address, publicHex, privateHex, derivationPath);
const identity = JSON.stringify(newId);
await accountsDB.open();
await accountsDB.accounts.add({
dateCreated: new Date().toISOString(),
derivationPath: derivationPath,
did: newId.did,
identity: identity,
mnemonic: mnemonic,
publicKeyHex: newId.keys[0].publicKeyHex,
});
await db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: newId.did,
});
return newId.did;
};
export const sendTestThroughPushServer = async ( export const sendTestThroughPushServer = async (
subscription: PushSubscription, subscription: PushSubscription,
skipFilter: boolean, skipFilter: boolean,

120
src/views/AccountViewView.vue

@ -4,6 +4,16 @@
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto"> <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
<!-- Back -->
<div class="text-lg text-center font-light relative px-7">
<h1
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
@click="$router.back()"
>
<fa icon="chevron-left" class="fa-fw"></fa>
</h1>
</div>
<!-- Heading --> <!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-4"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-4">
Your Identity Your Identity
@ -51,24 +61,6 @@
</router-link> </router-link>
</div> </div>
<!-- Registration notice -->
<!-- We won't show any loading indicator because it usually doesn't change anything. We'll just pop the message in only if we discover that they need it. -->
<div
v-if="!loadingLimits && !limits?.nextWeekBeginDateTime"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
>
<p class="mb-4">
<b>Note:</b> Before you can publicly announce a new project or time
commitment, a friend needs to register you.
</p>
<router-link
:to="{ name: 'contact-qr' }"
class="inline-block text-md uppercase bg-amber-600 text-white px-4 py-2 rounded-md"
>
Share Your Info
</router-link>
</div>
<!-- Identity Details --> <!-- Identity Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<h2 v-if="givenName" class="text-xl font-semibold mb-2"> <h2 v-if="givenName" class="text-xl font-semibold mb-2">
@ -80,9 +72,9 @@
<span v-else> <span v-else>
<router-link <router-link
:to="{ name: 'new-edit-account' }" :to="{ name: 'new-edit-account' }"
class="block w-full text-center text-md text-blue-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2" class="block w-full text-center text-md bg-amber-200 text-blue-500 uppercase border border-dashed border-slate-400 px-1.5 py-2 rounded-md mb-2"
> >
(Set Your Name) Set Your Name
</router-link> </router-link>
</span> </span>
@ -101,6 +93,24 @@
</div> </div>
</div> </div>
<!-- Registration notice -->
<!-- We won't show any loading indicator because it usually doesn't change anything. We'll just pop the message in only if we discover that they need it. -->
<div
v-if="!loadingLimits && !limits?.nextWeekBeginDateTime"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
>
<p class="mb-4">
<b>Note:</b> Before you can publicly announce a new project or time
commitment, a friend needs to register you.
</p>
<router-link
:to="{ name: 'contact-qr' }"
class="inline-block text-md uppercase bg-amber-600 text-white px-4 py-2 rounded-md"
>
Share Your Info
</router-link>
</div>
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"> <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
<div <div
v-if="!notificationMaybeChanged" v-if="!notificationMaybeChanged"
@ -135,41 +145,11 @@
</router-link> </router-link>
</div> </div>
<h3 class="text-sm uppercase font-semibold mb-3">Data Export</h3> <div
<router-link
:to="{ name: 'seed-backup' }"
v-if="activeDid" v-if="activeDid"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2" class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
>
Backup Identifier Seed
</router-link>
<button
v-bind:class="computedStartDownloadLinkClassNames()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
@click="exportDatabase()"
>
Download Settings & Contacts
<br />
(excluding Identifier Data)
</button>
<a
ref="downloadLink"
v-bind:class="computedDownloadLinkClassNames()"
class="block w-full text-center text-md uppercase bg-green-600 text-white px-1.5 py-2 rounded-md mb-6"
> >
If no download happened yet, click again here to download now. <div class="mb-2">Usage Limits</div>
</a>
<div v-if="activeDid" class="flex mt-8 py-2">
<h3 class="text-sm uppercase font-semibold">Rate Limits</h3>
<button
class="block w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md ml-2 mr-2 mb-2"
@click="checkLimits()"
>
Check Limits
</button>
<!-- show spinner if loading limits --> <!-- show spinner if loading limits -->
<div v-if="loadingLimits" class="text-center"> <div v-if="loadingLimits" class="text-center">
Checking&hellip; <fa icon="spinner" class="fa-spin"></fa> Checking&hellip; <fa icon="spinner" class="fa-spin"></fa>
@ -200,6 +180,40 @@
</b> </b>
</p> </p>
</div> </div>
<button
class="block float-right w-fit text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-2"
@click="checkLimits()"
>
Recheck Limits
</button>
</div>
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8">
<div>Data Export</div>
<router-link
:to="{ name: 'seed-backup' }"
v-if="activeDid"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2 mt-2"
>
Backup Identifier Seed
</router-link>
<button
v-bind:class="computedStartDownloadLinkClassNames()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
@click="exportDatabase()"
>
Download Settings & Contacts
<br />
(excluding Identifier Data)
</button>
<a
ref="downloadLink"
v-bind:class="computedDownloadLinkClassNames()"
class="block w-full text-center text-md uppercase bg-green-600 text-white px-1.5 py-2 rounded-md"
>
If no download happened yet, click again here to download now.
</a>
</div> </div>
<!-- id used by puppeteer test script --> <!-- id used by puppeteer test script -->

2
src/views/ContactsView.vue

@ -604,7 +604,7 @@ export default class ContactsView extends Vue {
group: "alert", group: "alert",
type: "info", type: "info",
title: "New User?", title: "New User?",
text: "If they are a new user, be sure to register them.", text: "If they are a new user, be sure to register to onboard them.",
}, },
-1, -1,
); );

12
src/views/HelpView.vue

@ -69,6 +69,18 @@
make it an opportunity to get to know their projects, and show your own. make it an opportunity to get to know their projects, and show your own.
</p> </p>
<h2 class="text-xl font-semibold">
I had an identifier, but I reinstalled and I got a new one automatically.
How do I restore my old one?
</h2>
<p>
Go to the page
<router-link class="text-blue-500" to="/import-account">import your identifier</router-link>.
If you don't want the old one, click "Advanced" and check the box to erase it.
(The erase option only shows if you have exactly one identifier.
For more in-depth surgery, you'll have to erase data from the browser or reinstall.)
</p>
<h2 class="text-xl font-semibold">How do I add someone else?</h2> <h2 class="text-xl font-semibold">How do I add someone else?</h2>
<p> <p>
<button class="text-blue-500" @click="showOnboardInfo"> <button class="text-blue-500" @click="showOnboardInfo">

53
src/views/HomeView.vue

@ -61,8 +61,13 @@
<!-- show the actions for recognizing a give --> <!-- show the actions for recognizing a give -->
<div class="mb-8"> <div class="mb-8">
<div v-if="isCreatingIdentifier">
<p class="text-slate-500 text-center italic mt-4 mb-4">
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</p>
</div>
<div <div
v-if="!activeDid" v-if="!activeDid && !isCreatingIdentifier"
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4" class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
> >
<p class="text-lg mb-3"> <p class="text-lg mb-3">
@ -81,19 +86,19 @@
v-else-if="!isRegistered" v-else-if="!isRegistered"
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4" class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
> >
Someone must register your account before you can record anyone's gives. Someone must register your identifier before you can record anyone's
To do this: giving.
<router-link <router-link
:to="{ name: 'contact-qr' }" :to="{ name: 'contact-qr' }"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md" class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 mb-4 px-2 py-3 rounded-md"
>
1. Show Them Your Identifier Info</router-link
> >
<router-link Show Them Your Identifier Info</router-link
:to="{ name: 'account' }"
class="block text-center text-md font-bold uppercase bg-blue-500 text-white mt-2 px-2 py-3 rounded-md"
> >
2. Check Your Limits</router-link To double-check that you're registered,
<br />
<router-link :to="{ name: 'account' }" class="text-blue-500">
see your Usage Limits on the Account
<fa icon="circle-user" /> page.</router-link
> >
</div> </div>
@ -171,7 +176,7 @@
class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm" class="border-b border-dashed border-slate-400 text-orange-400 pb-2 mb-2 font-bold uppercase text-sm"
v-if="record.jwtId == feedLastViewedClaimId" v-if="record.jwtId == feedLastViewedClaimId"
> >
You've seen all the following You've seen all the following before
</div> </div>
<div class="flex"> <div class="flex">
@ -184,7 +189,7 @@
</li> </li>
</ul> </ul>
</InfiniteScroll> </InfiniteScroll>
<div :class="{ hidden: isHiddenSpinner }"> <div v-if="isFeedLoading">
<p class="text-slate-500 text-center italic mt-4 mb-4"> <p class="text-slate-500 text-center italic mt-4 mb-4">
<fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip; <fa icon="spinner" class="fa-spin-pulse"></fa> Loading&hellip;
</p> </p>
@ -213,6 +218,7 @@ import {
GiveServerRecord, GiveServerRecord,
} from "@/libs/endorserServer"; } from "@/libs/endorserServer";
import { IIdentifier } from "@veramo/core"; import { IIdentifier } from "@veramo/core";
import { generateSaveAndActivateIdentity } from "@/libs/util";
interface Notification { interface Notification {
group: string; group: string;
@ -240,16 +246,11 @@ export default class HomeView extends Vue {
feedData = []; feedData = [];
feedPreviousOldestId?: string; feedPreviousOldestId?: string;
feedLastViewedClaimId?: string; feedLastViewedClaimId?: string;
isHiddenSpinner = true; isCreatingIdentifier = false;
isFeedLoading = true;
isRegistered = false; isRegistered = false;
numAccounts = 0;
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
async beforeCreate() {
await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();
}
public async getIdentity(activeDid: string) { public async getIdentity(activeDid: string) {
await accountsDB.open(); await accountsDB.open();
const account = (await accountsDB.accounts const account = (await accountsDB.accounts
@ -283,8 +284,16 @@ export default class HomeView extends Vue {
this.feedLastViewedClaimId = settings?.lastViewedClaimId; this.feedLastViewedClaimId = settings?.lastViewedClaimId;
this.isRegistered = !!settings?.isRegistered; this.isRegistered = !!settings?.isRegistered;
if (this.allMyDids.length === 0) {
this.isCreatingIdentifier = true;
this.activeDid = await generateSaveAndActivateIdentity();
this.allMyDids = [this.activeDid];
this.isCreatingIdentifier = false;
}
// this returns a Promise but we don't need to wait for it // this returns a Promise but we don't need to wait for it
this.updateAllFeed();
await this.updateAllFeed();
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) { } catch (err: any) {
@ -344,7 +353,7 @@ export default class HomeView extends Vue {
} }
public async updateAllFeed() { public async updateAllFeed() {
this.isHiddenSpinner = false; this.isFeedLoading = true;
await this.retrieveGives(this.apiServer, this.feedPreviousOldestId) await this.retrieveGives(this.apiServer, this.feedPreviousOldestId)
.then(async (results) => { .then(async (results) => {
if (results.data.length > 0) { if (results.data.length > 0) {
@ -375,7 +384,7 @@ export default class HomeView extends Vue {
-1, -1,
); );
}); });
this.isHiddenSpinner = true; this.isFeedLoading = false;
} }
/** /**

29
src/views/ImportAccountView.vue

@ -36,17 +36,28 @@
Enter a custom derivation path Enter a custom derivation path
<input <input
type="text" type="text"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2" class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
v-model="derivationPath" v-model="derivationPath"
/> />
<span class="ml-4">
For previous uPort or Endorser users, For previous uPort or Endorser users,
<a @click="derivationPath = UPORT_DERIVATION_PATH" class="text-blue-500"> <a
@click="derivationPath = UPORT_DERIVATION_PATH"
class="text-blue-500"
>
click here to use that value. click here to use that value.
</a> </a>
</span>
<div class="mt-4" v-if="numAccounts == 1">
<input type="checkbox" class="mr-2" v-model="shouldErase" />
<label>Erase the previous identifier.</label>
</div> </div>
</div>
<div class="mt-8"> <div class="mt-8">
<button <button
@click="from_mnemonic()" @click="fromMnemonic()"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2" class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
> >
Import Import
@ -89,16 +100,23 @@ export default class ImportAccountView extends Vue {
mnemonic = ""; mnemonic = "";
address = ""; address = "";
numAccounts = 0;
privateHex = ""; privateHex = "";
publicHex = ""; publicHex = "";
derivationPath = DEFAULT_ROOT_DERIVATION_PATH; derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
showAdvanced = false; showAdvanced = false;
shouldErase = false;
async created() {
await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();
}
public onCancelClick() { public onCancelClick() {
this.$router.back(); this.$router.back();
} }
public async from_mnemonic() { public async fromMnemonic() {
const mne: string = this.mnemonic.trim().toLowerCase(); const mne: string = this.mnemonic.trim().toLowerCase();
try { try {
[this.address, this.privateHex, this.publicHex] = deriveAddress( [this.address, this.privateHex, this.publicHex] = deriveAddress(
@ -114,6 +132,9 @@ export default class ImportAccountView extends Vue {
); );
await accountsDB.open(); await accountsDB.open();
if (this.shouldErase) {
await accountsDB.accounts.clear();
}
await accountsDB.accounts.add({ await accountsDB.accounts.add({
dateCreated: new Date().toISOString(), dateCreated: new Date().toISOString(),
derivationPath: this.derivationPath, derivationPath: this.derivationPath,

7
src/views/NewEditProjectView.vue

@ -168,9 +168,12 @@ export default class NewEditProjectView extends Vue {
description: "", description: "",
}; // this default is only to avoid errors before plan is loaded }; // this default is only to avoid errors before plan is loaded
includeLocation = false; includeLocation = false;
isHiddenSave = false;
isHiddenSpinner = true;
latitude = 0; latitude = 0;
longitude = 0; longitude = 0;
numAccounts = 0; numAccounts = 0;
projectId = localStorage.getItem("projectId") || "";
projectIssuerDid = ""; projectIssuerDid = "";
zoom = 2; zoom = 2;
@ -204,10 +207,6 @@ export default class NewEditProjectView extends Vue {
return headers; return headers;
} }
projectId = localStorage.getItem("projectId") || "";
isHiddenSave = false;
isHiddenSpinner = true;
async created() { async created() {
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);

27
src/views/NewIdentifierView.vue

@ -54,9 +54,7 @@
<script lang="ts"> <script lang="ts">
import "dexie-export-import"; import "dexie-export-import";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB, db } from "@/db/index"; import { generateSaveAndActivateIdentity } from "@/libs/util";
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import QuickNav from "@/components/QuickNav.vue"; import QuickNav from "@/components/QuickNav.vue";
@Component({ components: { QuickNav } }) @Component({ components: { QuickNav } })
@ -64,28 +62,7 @@ export default class NewIdentifierView extends Vue {
loading = true; loading = true;
async mounted() { async mounted() {
const mnemonic = generateSeed(); await generateSaveAndActivateIdentity();
// address is 0x... ETH address, without "did:eth:"
const [address, privateHex, publicHex, derivationPath] =
deriveAddress(mnemonic);
const newId = newIdentifier(address, publicHex, privateHex, derivationPath);
const identity = JSON.stringify(newId);
await accountsDB.open();
await accountsDB.accounts.add({
dateCreated: new Date().toISOString(),
derivationPath: derivationPath,
did: newId.did,
identity: identity,
mnemonic: mnemonic,
publicKeyHex: newId.keys[0].publicKeyHex,
});
await db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: newId.did,
});
this.loading = false; this.loading = false;
setTimeout(() => { setTimeout(() => {
this.$router.push({ name: "home" }); this.$router.push({ name: "home" });

Loading…
Cancel
Save