forked from trent_larson/crowd-funder-for-time-pwa
Compare commits
1 Commits
more-small
...
friend-tec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7f15fde25 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,14 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
|
|
||||||
## [0.1.2] - 2023.11.01
|
|
||||||
### Added
|
|
||||||
- Basics: create ID, record a give, declare a project, search, and get notifications.
|
|
||||||
18
README.md
18
README.md
@@ -1,9 +1,6 @@
|
|||||||
# kickstart-for-time-pwa
|
# kickstart-for-time-pwa
|
||||||
|
|
||||||
## Project setup
|
## Project setup
|
||||||
|
|
||||||
We have pkgx.dev set up in package.json, so you can use `dev` to set up the dev environment.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
@@ -26,12 +23,6 @@ npm run build
|
|||||||
npm run lint
|
npm run lint
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
###
|
|
||||||
|
|
||||||
For your own web-push tests, change the 'vapid' URL in App.vue, and install apps on the same domain.
|
|
||||||
|
|
||||||
### Test key contents
|
### Test key contents
|
||||||
|
|
||||||
See [this page](openssl_signing_console.rst)
|
See [this page](openssl_signing_console.rst)
|
||||||
@@ -95,6 +86,15 @@ Clear cache for localhost, then go to http://localhost:8080/start
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
See https://tea.xyz
|
||||||
|
|
||||||
|
| Project | Version |
|
||||||
|
| ---------- | --------- |
|
||||||
|
| nodejs.org | ^16.0.0 |
|
||||||
|
| npmjs.com | ^8.0.0 |
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
||||||
### Reference Material
|
### Reference Material
|
||||||
|
|||||||
22244
package-lock.json
generated
22244
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kickstart-for-time-pwa",
|
"name": "kickstart-for-time-pwa",
|
||||||
"version": "0.1.3",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
@@ -52,7 +52,6 @@
|
|||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-axios": "^3.5.2",
|
"vue-axios": "^3.5.2",
|
||||||
"vue-facing-decorator": "^2.1.20",
|
"vue-facing-decorator": "^2.1.20",
|
||||||
"vue-qrcode-reader": "^5.3.4",
|
|
||||||
"vue-router": "^4.2.3",
|
"vue-router": "^4.2.3",
|
||||||
"web-did-resolver": "^2.0.27"
|
"web-did-resolver": "^2.0.27"
|
||||||
},
|
},
|
||||||
@@ -81,6 +80,5 @@
|
|||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "^3.3.2",
|
||||||
"typescript": "~5.1.6"
|
"typescript": "~5.1.6"
|
||||||
},
|
}
|
||||||
"pkgx": "node^18 npm^10"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
|
||||||
|
- 08 Scan QR code to import into contacts assignee:matthew
|
||||||
|
- SEE - https://github.com/gruhn/vue-qrcode-reader
|
||||||
|
|
||||||
- in endorser-push-server - mount folder for persistent sqlite DB outside of container
|
- in endorser-push-server - mount folder for persistent sqlite DB outside of container
|
||||||
- test alerts on all pages -- or refactor to new "notify" (since AlertMessage refactoring may require a change, et. ContactQRScanShowView)
|
- test alerts on all pages -- or refactor to new "notify" (since AlertMessage refactoring may require a change, et. ContactQRScanShowView)
|
||||||
|
- .2 bug - on contacts view, click on "to" & "from" and nothing happens
|
||||||
- 40 notifications :
|
- 40 notifications :
|
||||||
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew
|
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew
|
||||||
|
|
||||||
- 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:matthew
|
- 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:matthew
|
||||||
|
|
||||||
- 01 fix the Discovery map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui
|
- 01 fix the Discovery map display to not show on top of bottom icons (and any other UI tweaks on the map flow) assignee-group:ui
|
||||||
- .1 add instructions for map location selection
|
|
||||||
|
|
||||||
- 01 Show pop-up or some message confirming that settings & contacts download has been initiated/finished assignee:matthew assignee-group:ui
|
- 01 Show pop-up or some message confirming that settings & contacts download has been initiated/finished assignee:matthew assignee-group:ui
|
||||||
|
|
||||||
@@ -23,23 +26,17 @@ tasks:
|
|||||||
|
|
||||||
- 24 Move to Vite assignee:matthew
|
- 24 Move to Vite assignee:matthew
|
||||||
|
|
||||||
- .2 fit as many icons as possible on home & project view screens but only going halfway down the page
|
- .2 Edit Plan does not have icons across the bottom assignee-group:ui
|
||||||
- .1 Remove notification alert visuals on home page
|
- .5 include the hash of the latest commit, and maybe a version
|
||||||
- .5 Add infinite scroll to gifts on the home page
|
|
||||||
- .5 bug - search for "Safari" does not find the project, but if already on the "Anywhere" tab it shows all
|
|
||||||
- .2 figure out why endorser-mobile search doesn't find recently created PlanAction
|
|
||||||
- .1 when creating a plan, select location and then make sure you can deselect on Android
|
|
||||||
- .5 include a version, maybe the hash of the latest commit -- figuring out how it works on prod now
|
|
||||||
- .5 add link to further project / people when a project pays ahead
|
- .5 add link to further project / people when a project pays ahead
|
||||||
- .5 add project ID to the URL, to make a project publicly-accessible
|
- .5 add project ID to the URL, to make a project publicly-accessible
|
||||||
|
- .5 remove edit from project page for projects owned by others
|
||||||
- .5 fix where user 0 sees no txns from user 1 on contacts page but sees them on list page
|
- .5 fix where user 0 sees no txns from user 1 on contacts page but sees them on list page
|
||||||
- .2 on ProjectViewView, show different messages for "to" and "from" sections if none exist assignee-group:ui
|
- .2 on ProjectViewView, show different messages for "to" and "from" sections if none exist assignee-group:ui
|
||||||
- .2 fix static icon to the right on project page (Matthew - I've made "Rotary" into issuer?) assignee:jose assignee-group:ui
|
- .2 fix static icon to the right on project page (Matthew - I've made "Rotary" into issuer?) assignee:jose assignee-group:ui
|
||||||
- .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent
|
- .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent
|
||||||
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164
|
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164
|
||||||
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
|
||||||
- .1 Make give description text box into something that expands as they type
|
|
||||||
- .1 Make contact info specific to Time Safari - rather pointing at CommunityCred.org
|
|
||||||
|
|
||||||
- Discuss whether the remaining tasks are worthwhile before MVP release.
|
- Discuss whether the remaining tasks are worthwhile before MVP release.
|
||||||
|
|
||||||
@@ -52,9 +49,6 @@ tasks:
|
|||||||
- .2 Show a warning if both giver and recipient are the same (but still allow?) assignee-group:ui
|
- .2 Show a warning if both giver and recipient are the same (but still allow?) assignee-group:ui
|
||||||
- 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui
|
- 01 Would it look better to shrink the buttons on many pages so they don't expand to the width of the screen? assignee-group:ui
|
||||||
- .5 Display a more appealing confirmation on the map when erasing the marker assignee-group:ui
|
- .5 Display a more appealing confirmation on the map when erasing the marker assignee-group:ui
|
||||||
- .5 make a VC details page
|
|
||||||
- .1 Add units or different icon to the coins (to distinguish $, BTC, hours, etc)
|
|
||||||
- .1 remove firstName (& lastName) from localStorage
|
|
||||||
|
|
||||||
- contacts v+ :
|
- contacts v+ :
|
||||||
- 01 Import all the non-sensitive data (ie. contacts & settings).
|
- 01 Import all the non-sensitive data (ie. contacts & settings).
|
||||||
@@ -78,11 +72,6 @@ tasks:
|
|||||||
- Test PWA features on Android and iOS.
|
- Test PWA features on Android and iOS.
|
||||||
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
|
blocks: ref:https://raw.githubusercontent.com/trentlarson/lives-of-gifts/master/project.yaml#kickstarter%20for%20time
|
||||||
|
|
||||||
- .5 show seed phrase in a QR code for transfer to another device
|
|
||||||
|
|
||||||
- 32 accept images for projects
|
|
||||||
- 32 accept images for contacts
|
|
||||||
|
|
||||||
- linking between projects or plans :
|
- linking between projects or plans :
|
||||||
- show total time given to & from a project
|
- show total time given to & from a project
|
||||||
- terminology:
|
- terminology:
|
||||||
|
|||||||
69
src/App.vue
69
src/App.vue
@@ -246,6 +246,75 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="notification.type === 'pwa-install-gate-ios'"
|
||||||
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||||
|
>
|
||||||
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
||||||
|
<fa
|
||||||
|
icon="mobile-screen-button"
|
||||||
|
class="inline-block text-7xl text-slate-400 mb-4"
|
||||||
|
>
|
||||||
|
</fa>
|
||||||
|
<h3 class="text-2xl font-semibold mb-4">Add to Home Screen</h3>
|
||||||
|
<p class="text-md mb-4">
|
||||||
|
To install the app, you need to add this website to your home
|
||||||
|
screen.
|
||||||
|
</p>
|
||||||
|
<p class="text-md">
|
||||||
|
In your Safari browser menu, tap the
|
||||||
|
<span class="whitespace-nowrap">
|
||||||
|
<fa
|
||||||
|
icon="arrow-up-from-bracket"
|
||||||
|
class="fa-fw text-slate-500 bg-slate-200 py-1 -my-1 px-0.5 rounded"
|
||||||
|
>
|
||||||
|
</fa>
|
||||||
|
Share
|
||||||
|
</span>
|
||||||
|
icon and choose
|
||||||
|
<b>Add to Home Screen</b> in the options. Then, open the Time
|
||||||
|
Safari app on your home screen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="notification.type === 'pwa-install-gate-android'"
|
||||||
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
||||||
|
>
|
||||||
|
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
||||||
|
<fa
|
||||||
|
icon="mobile-screen-button"
|
||||||
|
class="inline-block text-7xl text-slate-400 mb-4"
|
||||||
|
>
|
||||||
|
</fa>
|
||||||
|
<h3 class="text-2xl font-semibold mb-4">Add to Home Screen</h3>
|
||||||
|
<p class="text-md mb-4">
|
||||||
|
To install the app, you need to add this website to your home
|
||||||
|
screen.
|
||||||
|
</p>
|
||||||
|
<p class="text-md">
|
||||||
|
In your Chrome browser menu, tap the
|
||||||
|
<span class="whitespace-nowrap">
|
||||||
|
<fa
|
||||||
|
icon="ellipsis-vertical"
|
||||||
|
class="fa-fw text-slate-500 bg-slate-200 py-1 -my-1 px-0.5 rounded"
|
||||||
|
>
|
||||||
|
</fa>
|
||||||
|
More
|
||||||
|
</span>
|
||||||
|
button and choose
|
||||||
|
<b>Install App</b> in the options.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Notification>
|
</Notification>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div v-if="visible" class="dialog-overlay">
|
<div v-if="visible" class="dialog-overlay">
|
||||||
<div class="dialog">
|
<div class="dialog">
|
||||||
<h1 class="text-xl font-bold text-center mb-4">
|
<h1 class="text-xl font-bold text-center mb-4">
|
||||||
{{ message }} {{ giver?.name || "somebody not named" }}
|
{{ message }} {{ giver?.name || "somebody not specified" }}
|
||||||
</h1>
|
</h1>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -51,57 +51,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
import { Vue, Component, Prop, Emit } from "vue-facing-decorator";
|
||||||
import { createAndSubmitGive, GiverInputInfo } from "@/libs/endorserServer";
|
import { GiverInputInfo, GiverOutputInfo } from "@/libs/endorserServer";
|
||||||
import { accountsDB, db } from "@/db/index";
|
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
|
||||||
import { Account } from "@/db/tables/accounts";
|
|
||||||
|
|
||||||
interface Notification {
|
|
||||||
group: string;
|
|
||||||
type: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class GiftedDialog extends Vue {
|
export default class GiftedDialog extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
|
||||||
|
|
||||||
@Prop message = "";
|
@Prop message = "";
|
||||||
@Prop projectId = "";
|
|
||||||
|
|
||||||
activeDid = "";
|
|
||||||
apiServer = "";
|
|
||||||
|
|
||||||
giver?: GiverInputInfo;
|
giver?: GiverInputInfo;
|
||||||
description = "";
|
description = "";
|
||||||
hours = "0";
|
hours = "0";
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|
||||||
async created() {
|
|
||||||
try {
|
|
||||||
await db.open();
|
|
||||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
|
||||||
this.apiServer = settings?.apiServer || "";
|
|
||||||
this.activeDid = settings?.activeDid || "";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
} catch (err: any) {
|
|
||||||
console.log("Error retrieving settings from database:", err);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text:
|
|
||||||
err.message ||
|
|
||||||
"There was an error retrieving the latest sweet, sweet action.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open(giver: GiverInputInfo) {
|
open(giver: GiverInputInfo) {
|
||||||
this.giver = giver;
|
this.giver = giver;
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
@@ -119,169 +80,27 @@ export default class GiftedDialog extends Vue {
|
|||||||
this.hours = `${Math.max(0, (parseFloat(this.hours) || 1) - 1)}`;
|
this.hours = `${Math.max(0, (parseFloat(this.hours) || 1) - 1)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
@Emit("dialog-result")
|
||||||
|
confirm(): GiverOutputInfo {
|
||||||
|
const result = {
|
||||||
|
action: "confirm",
|
||||||
|
giver: this.giver,
|
||||||
|
hours: parseFloat(this.hours),
|
||||||
|
description: this.description,
|
||||||
|
};
|
||||||
this.close();
|
this.close();
|
||||||
this.description = "";
|
this.description = "";
|
||||||
this.giver = undefined;
|
this.giver = undefined;
|
||||||
this.hours = "0";
|
this.hours = "0";
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirm() {
|
@Emit("dialog-result")
|
||||||
|
cancel(): GiverOutputInfo {
|
||||||
|
const result = { action: "cancel" };
|
||||||
this.close();
|
this.close();
|
||||||
this.$notify(
|
return result;
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "toast",
|
|
||||||
text: "Recording the give...",
|
|
||||||
title: "",
|
|
||||||
},
|
|
||||||
1000,
|
|
||||||
);
|
|
||||||
// this is asynchronous, but we don't need to wait for it to complete
|
|
||||||
this.recordGive(
|
|
||||||
this.giver?.did as string | undefined,
|
|
||||||
this.description,
|
|
||||||
parseFloat(this.hours),
|
|
||||||
).then(() => {
|
|
||||||
this.description = "";
|
|
||||||
this.giver = undefined;
|
|
||||||
this.hours = "0";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
|
||||||
await accountsDB.open();
|
|
||||||
const account = (await accountsDB.accounts
|
|
||||||
.where("did")
|
|
||||||
.equals(activeDid)
|
|
||||||
.first()) as Account;
|
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
|
||||||
|
|
||||||
if (!identity) {
|
|
||||||
throw new Error(
|
|
||||||
"Attempted to load Give records for DID ${activeDid} but no identity was found",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param giverDid may be null
|
|
||||||
* @param description may be an empty string
|
|
||||||
* @param hours may be 0
|
|
||||||
*/
|
|
||||||
public async recordGive(
|
|
||||||
giverDid?: string,
|
|
||||||
description?: string,
|
|
||||||
hours?: number,
|
|
||||||
) {
|
|
||||||
if (!this.activeDid) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "You must select an identity before you can record a give.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!description && !hours) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "You must enter a description or some number of hours.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
|
||||||
const result = await createAndSubmitGive(
|
|
||||||
this.axios,
|
|
||||||
this.apiServer,
|
|
||||||
identity,
|
|
||||||
giverDid,
|
|
||||||
this.activeDid,
|
|
||||||
description,
|
|
||||||
hours,
|
|
||||||
this.projectId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
result.type === "error" ||
|
|
||||||
this.isGiveCreationError(result.response)
|
|
||||||
) {
|
|
||||||
const errorMessage = this.getGiveCreationErrorMessage(result);
|
|
||||||
console.log("Error with give creation result:", result);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: errorMessage || "There was an error creating the give.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "success",
|
|
||||||
title: "Success",
|
|
||||||
text: "That gift was recorded.",
|
|
||||||
},
|
|
||||||
10000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log("Error with give recordation caught:", error);
|
|
||||||
const message =
|
|
||||||
error.userMessage ||
|
|
||||||
error.response?.data?.error?.message ||
|
|
||||||
"There was an error recording the give.";
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions for readability
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param result response "data" from the server
|
|
||||||
* @returns true if the result indicates an error
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
isGiveCreationError(result: any) {
|
|
||||||
return result.status !== 201 || result.data?.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param result direct response eg. ErrorResult or SuccessResult (potentially with embedded "data")
|
|
||||||
* @returns best guess at an error message
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
getGiveCreationErrorMessage(result: any) {
|
|
||||||
return (
|
|
||||||
result.error?.userMessage ||
|
|
||||||
result.error?.error ||
|
|
||||||
result.response?.data?.error?.message
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Generic strings that could be used throughout the app.
|
* Generic strings that could be used throughout the app.
|
||||||
*
|
|
||||||
* See also ../libs/veramo/setup.ts
|
|
||||||
*/
|
*/
|
||||||
export enum AppString {
|
export enum AppString {
|
||||||
APP_NAME = "Time Safari",
|
APP_NAME = "Kick-Start with Time",
|
||||||
|
|
||||||
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
|
||||||
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
TEST_ENDORSER_API_SERVER = "https://test-api.endorser.ch",
|
||||||
@@ -12,13 +10,3 @@ export enum AppString {
|
|||||||
|
|
||||||
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
|
DEFAULT_ENDORSER_API_SERVER = TEST_ENDORSER_API_SERVER,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* See notiwind package
|
|
||||||
*/
|
|
||||||
export interface NotificationIface {
|
|
||||||
group: string;
|
|
||||||
type: string; // "toast" | "info" | "success" | "warning" | "danger"
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ export interface Contact {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ContactsSchema = {
|
export const ContactsSchema = {
|
||||||
contacts: "&did, name, publicKeyBase64, registered, seesMe",
|
contacts: "++did, name, publicKeyBase64, registered, seesMe",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ export type Settings = {
|
|||||||
activeDid?: string;
|
activeDid?: string;
|
||||||
apiServer?: string;
|
apiServer?: string;
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
isRegistered?: boolean;
|
lastName?: string;
|
||||||
lastName?: string; // deprecated, pre v 0.1.3
|
|
||||||
lastViewedClaimId?: string;
|
lastViewedClaimId?: string;
|
||||||
searchBoxes?: Array<{
|
searchBoxes?: Array<{
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
|
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
|
||||||
import { getRandomBytesSync } from "ethereum-cryptography/random";
|
import { getRandomBytesSync } from "ethereum-cryptography/random";
|
||||||
import { entropyToMnemonic } from "ethereum-cryptography/bip39";
|
import { entropyToMnemonic } from "ethereum-cryptography/bip39";
|
||||||
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
|
import { wordlist } from "ethereum-cryptography/bip39/wordlists/english";
|
||||||
@@ -6,10 +7,7 @@ import { HDNode } from "@ethersproject/hdnode";
|
|||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import * as u8a from "uint8arrays";
|
import * as u8a from "uint8arrays";
|
||||||
|
|
||||||
import { ENDORSER_JWT_URL_LOCATION } from "@/libs/endorserServer";
|
export const DEFAULT_ROOT_DERIVATION_PATH = "m/76798669'/0'/0'/0'";
|
||||||
import { DEFAULT_DID_PROVIDER_NAME } from "../veramo/setup";
|
|
||||||
|
|
||||||
export const DEFAULT_ROOT_DERIVATION_PATH = "m/84737769'/0'/0'/0'";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -152,24 +150,3 @@ export function fromJose(signature: string): {
|
|||||||
export function bytesToHex(b: Uint8Array): string {
|
export function bytesToHex(b: Uint8Array): string {
|
||||||
return u8a.toString(b, "base16");
|
return u8a.toString(b, "base16");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
@return results of uportJwtPayload:
|
|
||||||
{ iat: number, iss: string (DID), own: { name, publicEncKey (base64-encoded key) } }
|
|
||||||
|
|
||||||
Note that similar code is also contained in time-safari
|
|
||||||
*/
|
|
||||||
export const getContactPayloadFromJwtUrl = (jwtUrlText: string) => {
|
|
||||||
let jwtText = jwtUrlText;
|
|
||||||
const endorserContextLoc = jwtText.indexOf(ENDORSER_JWT_URL_LOCATION);
|
|
||||||
if (endorserContextLoc > -1) {
|
|
||||||
jwtText = jwtText.substring(
|
|
||||||
endorserContextLoc + ENDORSER_JWT_URL_LOCATION.length,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// JWT format: { header, payload, signature, data }
|
|
||||||
const jwt = didJwt.decodeJWT(jwtText);
|
|
||||||
|
|
||||||
return jwt.payload;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ import { Axios, AxiosResponse } from "axios";
|
|||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
|
|
||||||
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
export const SCHEMA_ORG_CONTEXT = "https://schema.org";
|
||||||
// the object in RegisterAction claims
|
|
||||||
export const SERVICE_ID = "endorser.ch";
|
export const SERVICE_ID = "endorser.ch";
|
||||||
// the prefix for the contact URL
|
|
||||||
export const CONTACT_URL_PREFIX = "https://endorser.ch";
|
|
||||||
// the suffix for the contact URL
|
|
||||||
export const ENDORSER_JWT_URL_LOCATION = "/contact?jwt=";
|
|
||||||
|
|
||||||
export interface AgreeVerifiableCredential {
|
export interface AgreeVerifiableCredential {
|
||||||
"@context": string;
|
"@context": string;
|
||||||
@@ -116,8 +111,6 @@ export function isHiddenDid(did: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY
|
always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY
|
||||||
|
|
||||||
Similar logic is found in endorser-mobile.
|
|
||||||
**/
|
**/
|
||||||
export function didInfo(
|
export function didInfo(
|
||||||
did: string,
|
did: string,
|
||||||
@@ -125,14 +118,14 @@ export function didInfo(
|
|||||||
allMyDids: string[],
|
allMyDids: string[],
|
||||||
contacts: Contact[],
|
contacts: Contact[],
|
||||||
): string {
|
): string {
|
||||||
if (!did) return "Someone Anonymous";
|
|
||||||
|
|
||||||
const myId = R.find(R.equals(did), allMyDids);
|
const myId = R.find(R.equals(did), allMyDids);
|
||||||
if (myId) return `You${myId !== activeDid ? " (Alt ID)" : ""}`;
|
if (myId) return `You${myId !== activeDid ? " (Alt ID)" : ""}`;
|
||||||
|
|
||||||
const contact = R.find((c) => c.did === did, contacts);
|
const contact = R.find((c) => c.did === did, contacts);
|
||||||
return contact
|
return contact
|
||||||
? contact.name || "Contact With No Name"
|
? contact.name || "Someone Unnamed in Contacts"
|
||||||
|
: !did
|
||||||
|
? "Unspecified Person"
|
||||||
: isHiddenDid(did)
|
: isHiddenDid(did)
|
||||||
? "Someone Not In Network"
|
? "Someone Not In Network"
|
||||||
: "Someone Not In Contacts";
|
: "Someone Not In Contacts";
|
||||||
|
|||||||
@@ -1,7 +1,151 @@
|
|||||||
// see also ../constants/app.ts and
|
// Created from the setup in https://veramo.io/docs/guides/react_native
|
||||||
|
|
||||||
|
// Core interfaces
|
||||||
|
/* import {
|
||||||
|
createAgent,
|
||||||
|
IDIDManager,
|
||||||
|
IResolver,
|
||||||
|
IDataStore,
|
||||||
|
IKeyManager,
|
||||||
|
} from "@veramo/core";
|
||||||
|
*/
|
||||||
|
// Core identity manager plugin
|
||||||
|
//import { DIDManager } from "@veramo/did-manager";
|
||||||
|
|
||||||
|
// Ethr did identity provider
|
||||||
|
//import { EthrDIDProvider } from "@veramo/did-provider-ethr";
|
||||||
|
|
||||||
|
// Core key manager plugin
|
||||||
|
//import { KeyManager } from "@veramo/key-manager";
|
||||||
|
|
||||||
|
// Custom key management system for RN
|
||||||
|
//import { KeyManagementSystem } from '@veramo/kms-local-react-native'
|
||||||
|
|
||||||
|
// Custom resolver
|
||||||
|
// Custom resolvers
|
||||||
|
//import { DIDResolverPlugin } from "@veramo/did-resolver";
|
||||||
|
/* import { Resolver } from "did-resolver";
|
||||||
|
import { getResolver as ethrDidResolver } from "ethr-did-resolver";
|
||||||
|
import { getResolver as webDidResolver } from "web-did-resolver";
|
||||||
|
*/
|
||||||
|
// for VCs and VPs https://veramo.io/docs/api/credential-w3c
|
||||||
|
//import { CredentialIssuer } from '@veramo/credential-w3c'
|
||||||
|
|
||||||
|
// Storage plugin using TypeOrm
|
||||||
|
/* import {
|
||||||
|
Entities,
|
||||||
|
KeyStore,
|
||||||
|
DIDStore,
|
||||||
|
IDataStoreORM,
|
||||||
|
} from "@veramo/data-store";
|
||||||
|
*/
|
||||||
|
// TypeORM is installed with @veramo/typeorm
|
||||||
|
//import { createConnection } from 'typeorm'
|
||||||
|
|
||||||
|
//import * as R from "ramda";
|
||||||
|
|
||||||
|
/*
|
||||||
|
import { Contact } from '../entity/contact'
|
||||||
|
import { Settings } from '../entity/settings'
|
||||||
|
import { PrivateData } from '../entity/privateData'
|
||||||
|
|
||||||
|
import { Initial1616938713828 } from '../migration/1616938713828-initial'
|
||||||
|
import { SettingsContacts1616967972293 } from '../migration/1616967972293-settings-contacts'
|
||||||
|
import { EncryptedSeed1637856484788 } from '../migration/1637856484788-EncryptedSeed'
|
||||||
|
import { HomeScreenConfig1639947962124 } from '../migration/1639947962124-HomeScreenConfig'
|
||||||
|
import { HandlePublicKeys1652142819353 } from '../migration/1652142819353-HandlePublicKeys'
|
||||||
|
import { LastClaimsSeen1656811846836 } from '../migration/1656811846836-LastClaimsSeen'
|
||||||
|
import { ContactRegistered1662256903367 }from '../migration/1662256903367-ContactRegistered'
|
||||||
|
import { PrivateData1663080623479 } from '../migration/1663080623479-PrivateData'
|
||||||
|
|
||||||
|
const ALL_ENTITIES = Entities.concat([Contact, Settings, PrivateData])
|
||||||
|
|
||||||
|
// Create react native DB connection configured by ormconfig.js
|
||||||
|
|
||||||
|
export const dbConnection = createConnection({
|
||||||
|
database: 'endorser-mobile.sqlite',
|
||||||
|
entities: ALL_ENTITIES,
|
||||||
|
location: 'default',
|
||||||
|
logging: ['error', 'info', 'warn'],
|
||||||
|
migrations: [ Initial1616938713828, SettingsContacts1616967972293, EncryptedSeed1637856484788, HomeScreenConfig1639947962124, HandlePublicKeys1652142819353, LastClaimsSeen1656811846836, ContactRegistered1662256903367, PrivateData1663080623479 ],
|
||||||
|
migrationsRun: true,
|
||||||
|
type: 'react-native',
|
||||||
|
})
|
||||||
|
*/
|
||||||
function didProviderName(netName: string) {
|
function didProviderName(netName: string) {
|
||||||
return "did:ethr" + (netName === "mainnet" ? "" : ":" + netName);
|
return "did:ethr" + (netName === "mainnet" ? "" : ":" + netName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_DID_PROVIDER_NAME = didProviderName("mainnet");
|
//const NETWORK_NAMES = ["mainnet", "rinkeby"];
|
||||||
|
|
||||||
|
const DEFAULT_DID_PROVIDER_NETWORK_NAME = "mainnet";
|
||||||
|
|
||||||
|
export const DEFAULT_DID_PROVIDER_NAME = didProviderName(
|
||||||
|
DEFAULT_DID_PROVIDER_NETWORK_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const HANDY_APP = false;
|
||||||
|
|
||||||
|
// this is used as the object in RegisterAction claims
|
||||||
|
export const SERVICE_ID = "endorser.ch";
|
||||||
|
|
||||||
|
//const INFURA_PROJECT_ID = "INFURA_PROJECT_ID";
|
||||||
|
/*
|
||||||
|
const providers = {}
|
||||||
|
NETWORK_NAMES.forEach((networkName) => {
|
||||||
|
providers[didProviderName(networkName)] = new EthrDIDProvider({
|
||||||
|
defaultKms: 'local',
|
||||||
|
network: networkName,
|
||||||
|
rpcUrl: 'https://' + networkName + '.infura.io/v3/' + INFURA_PROJECT_ID,
|
||||||
|
gas: 1000001,
|
||||||
|
ttl: 60 * 60 * 24 * 30 * 12 + 1,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const didManager = new DIDManager({
|
||||||
|
store: new DIDStore(dbConnection),
|
||||||
|
defaultProvider: DEFAULT_DID_PROVIDER_NAME,
|
||||||
|
providers: providers,
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* const basicDidResolvers = NETWORK_NAMES.map((networkName) => [
|
||||||
|
networkName,
|
||||||
|
new Resolver({
|
||||||
|
ethr: ethrDidResolver({
|
||||||
|
networks: [
|
||||||
|
{
|
||||||
|
name: networkName,
|
||||||
|
rpcUrl:
|
||||||
|
"https://" + networkName + ".infura.io/v3/" + INFURA_PROJECT_ID,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).ethr,
|
||||||
|
web: webDidResolver().web,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const basicResolverMap = R.fromPairs(basicDidResolvers)
|
||||||
|
|
||||||
|
export const DEFAULT_BASIC_RESOLVER = basicResolverMap[DEFAULT_DID_PROVIDER_NETWORK_NAME]
|
||||||
|
|
||||||
|
const agentDidResolvers = NETWORK_NAMES.map((networkName) => {
|
||||||
|
return new DIDResolverPlugin({
|
||||||
|
resolver: basicResolverMap[networkName],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
let allPlugins = [
|
||||||
|
new CredentialIssuer(),
|
||||||
|
new KeyManager({
|
||||||
|
store: new KeyStore(dbConnection),
|
||||||
|
kms: {
|
||||||
|
local: new KeyManagementSystem(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
didManager,
|
||||||
|
].concat(agentDidResolvers)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//export const agent = createAgent<IDIDManager & IKeyManager & IDataStore & IDataStoreORM & IResolver>({ plugins: allPlugins })
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { library } from "@fortawesome/fontawesome-svg-core";
|
|||||||
import {
|
import {
|
||||||
faArrowLeft,
|
faArrowLeft,
|
||||||
faArrowRight,
|
faArrowRight,
|
||||||
faBan,
|
faArrowUpFromBracket,
|
||||||
faBurst,
|
faBurst,
|
||||||
faCalendar,
|
faCalendar,
|
||||||
faChevronLeft,
|
faChevronLeft,
|
||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
faLongArrowAltLeft,
|
faLongArrowAltLeft,
|
||||||
faLongArrowAltRight,
|
faLongArrowAltRight,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
|
faMobileScreenButton,
|
||||||
faPen,
|
faPen,
|
||||||
faPersonCircleCheck,
|
faPersonCircleCheck,
|
||||||
faPersonCircleQuestion,
|
faPersonCircleQuestion,
|
||||||
@@ -60,7 +61,7 @@ import {
|
|||||||
library.add(
|
library.add(
|
||||||
faArrowLeft,
|
faArrowLeft,
|
||||||
faArrowRight,
|
faArrowRight,
|
||||||
faBan,
|
faArrowUpFromBracket,
|
||||||
faBurst,
|
faBurst,
|
||||||
faCalendar,
|
faCalendar,
|
||||||
faChevronLeft,
|
faChevronLeft,
|
||||||
@@ -87,6 +88,7 @@ library.add(
|
|||||||
faLongArrowAltLeft,
|
faLongArrowAltLeft,
|
||||||
faLongArrowAltRight,
|
faLongArrowAltRight,
|
||||||
faMagnifyingGlass,
|
faMagnifyingGlass,
|
||||||
|
faMobileScreenButton,
|
||||||
faPen,
|
faPen,
|
||||||
faPersonCircleCheck,
|
faPersonCircleCheck,
|
||||||
faPersonCircleQuestion,
|
faPersonCircleQuestion,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "home",
|
name: "home",
|
||||||
component: () =>
|
component: () =>
|
||||||
import(/* webpackChunkName: "home" */ "../views/HomeView.vue"),
|
import(/* webpackChunkName: "home" */ "../views/HomeView.vue"),
|
||||||
|
beforeEnter: enterOrStart,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/account",
|
path: "/account",
|
||||||
@@ -57,14 +58,6 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
/* webpackChunkName: "contact-amounts" */ "../views/ContactAmountsView.vue"
|
/* webpackChunkName: "contact-amounts" */ "../views/ContactAmountsView.vue"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "/contact-gives",
|
|
||||||
name: "contact-gives",
|
|
||||||
component: () =>
|
|
||||||
import(
|
|
||||||
/* webpackChunkName: "contact-gives" */ "../views/ContactGiftingView.vue"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "/contact-qr",
|
path: "/contact-qr",
|
||||||
name: "contact-qr",
|
name: "contact-qr",
|
||||||
@@ -78,6 +71,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "contacts",
|
name: "contacts",
|
||||||
component: () =>
|
component: () =>
|
||||||
import(/* webpackChunkName: "contacts" */ "../views/ContactsView.vue"),
|
import(/* webpackChunkName: "contacts" */ "../views/ContactsView.vue"),
|
||||||
|
beforeEnter: enterOrStart,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/scan-contact",
|
path: "/scan-contact",
|
||||||
@@ -191,10 +185,12 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/test",
|
path: "/contact-gives",
|
||||||
name: "test",
|
name: "contact-gives",
|
||||||
component: () =>
|
component: () =>
|
||||||
import(/* webpackChunkName: "test" */ "../views/TestView.vue"),
|
import(
|
||||||
|
/* webpackChunkName: "contact-gives" */ "../views/ContactGiftingView.vue"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import axios from "axios";
|
|||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
import { SERVICE_ID } from "../libs/endorserServer";
|
import { SERVICE_ID } from "../libs/veramo/setup";
|
||||||
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
import { deriveAddress, newIdentifier } from "../libs/crypto";
|
||||||
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
|
||||||
|
|||||||
@@ -52,17 +52,7 @@
|
|||||||
|
|
||||||
<!-- 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 class="text-xl font-semibold mb-2">{{ firstName }} {{ lastName }}</h2>
|
||||||
{{ givenName }}
|
|
||||||
</h2>
|
|
||||||
<span v-else>
|
|
||||||
<router-link
|
|
||||||
:to="{ name: 'new-edit-account' }"
|
|
||||||
class="text-xs bg-blue-500 text-white px-1.5 py-1 rounded-md"
|
|
||||||
>
|
|
||||||
(set name)
|
|
||||||
</router-link>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="text-slate-500 text-sm font-bold">ID</div>
|
<div class="text-slate-500 text-sm font-bold">ID</div>
|
||||||
<div class="text-sm text-slate-500 flex justify-start items-center mb-1">
|
<div class="text-sm text-slate-500 flex justify-start items-center mb-1">
|
||||||
@@ -77,11 +67,53 @@
|
|||||||
</button>
|
</button>
|
||||||
<span v-show="showDidCopy">Copied!</span>
|
<span v-show="showDidCopy">Copied!</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="text-slate-500 text-sm font-bold">Public Key (base 64)</div>
|
||||||
|
<div class="text-sm text-slate-500 flex justify-start items-center mb-1">
|
||||||
|
<code class="truncate">{{ publicBase64 }}</code>
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
doCopyTwoSecRedo(publicBase64, () => (showB64Copy = !showB64Copy))
|
||||||
|
"
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
|
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
<span v-show="showB64Copy">Copied!</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-slate-500 text-sm font-bold">Public Key (hex)</div>
|
||||||
|
<div class="text-sm text-slate-500 flex justify-start items-center mb-1">
|
||||||
|
<code class="truncate">{{ publicHex }}</code>
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
doCopyTwoSecRedo(publicHex, () => (showPubCopy = !showPubCopy))
|
||||||
|
"
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
|
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
<span v-show="showPubCopy">Copied!</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-slate-500 text-sm font-bold">Derivation Path</div>
|
||||||
|
<div class="text-sm text-slate-500 flex justify-start items-center mb-1">
|
||||||
|
<code class="truncate">{{ derivationPath }}</code>
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
doCopyTwoSecRedo(derivationPath, () => (showDerCopy = !showDerCopy))
|
||||||
|
"
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
|
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
||||||
|
</button>
|
||||||
|
<span v-show="showDerCopy">Copied!</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'new-edit-account' }"
|
:to="{ name: 'new-edit-account' }"
|
||||||
class="block text-center text-lg font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md mb-2"
|
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||||
>
|
>
|
||||||
Edit Identity
|
Edit Identity
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -100,10 +132,8 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<!-- label -->
|
|
||||||
<div>App Notifications</div>
|
|
||||||
<!-- toggle -->
|
<!-- toggle -->
|
||||||
<div class="relative ml-2">
|
<div class="relative">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input type="checkbox" name="toggleNotifications" class="sr-only" />
|
<input type="checkbox" name="toggleNotifications" class="sr-only" />
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
@@ -113,6 +143,8 @@
|
|||||||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- label -->
|
||||||
|
<div class="ml-2">App Notifications</div>
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
for="toggleMuteNotifications"
|
for="toggleMuteNotifications"
|
||||||
@@ -127,10 +159,8 @@
|
|||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<!-- label -->
|
|
||||||
<div>Mute Notifications</div>
|
|
||||||
<!-- toggle -->
|
<!-- toggle -->
|
||||||
<div class="relative ml-2">
|
<div class="relative">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -144,6 +174,8 @@
|
|||||||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- label -->
|
||||||
|
<div class="ml-2">Mute Notifications</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -160,13 +192,32 @@
|
|||||||
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
|
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()"
|
@click="exportDatabase()"
|
||||||
>
|
>
|
||||||
Download Settings & Contacts
|
Download Settings & Contacts (excluding Identifier Data)
|
||||||
<br />
|
|
||||||
(excluding Identifier Data)
|
|
||||||
</a>
|
</a>
|
||||||
<a ref="downloadLink" />
|
<a ref="downloadLink" />
|
||||||
|
|
||||||
<div v-if="activeDid" class="flex py-2">
|
<!-- QR code popup -->
|
||||||
|
<dialog id="dlgQR" class="backdrop:bg-black/75 rounded-md">
|
||||||
|
<div class="text-slate-500 text-center">
|
||||||
|
<b>ID:</b> <code>did:peer:kl45kj41lk451kl3</code>
|
||||||
|
</div>
|
||||||
|
<img src="/img/sample-qr-code.png" class="w-full mb-3" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
value="cancel"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||||
|
>
|
||||||
|
Copy to Clipboard
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
value="cancel"
|
||||||
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<div class="flex py-2">
|
||||||
<button class="text-center text-md text-blue-500" @click="checkLimits()">
|
<button class="text-center text-md text-blue-500" @click="checkLimits()">
|
||||||
Check Limits
|
Check Limits
|
||||||
</button>
|
</button>
|
||||||
@@ -193,84 +244,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- id used by puppeteer test script -->
|
|
||||||
<h3
|
<h3
|
||||||
id="advanced"
|
|
||||||
class="text-sm uppercase font-semibold mb-3"
|
class="text-sm uppercase font-semibold mb-3"
|
||||||
@click="showAdvanced = !showAdvanced"
|
@click="showAdvanced = !showAdvanced"
|
||||||
>
|
>
|
||||||
Advanced
|
Advanced
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div v-if="showAdvanced">
|
<div v-if="showAdvanced">
|
||||||
<!-- Deep Identity Details -->
|
|
||||||
<h2 class="text-slate-500 text-sm font-bold mb-2 py-2">
|
|
||||||
Deep Identity Details
|
|
||||||
</h2>
|
|
||||||
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
|
|
||||||
<div class="text-slate-500 text-sm font-bold">Public Key (base 64)</div>
|
|
||||||
<div
|
|
||||||
class="text-sm text-slate-500 flex justify-start items-center mb-1"
|
|
||||||
>
|
|
||||||
<code class="truncate">{{ publicBase64 }}</code>
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
doCopyTwoSecRedo(publicBase64, () => (showB64Copy = !showB64Copy))
|
|
||||||
"
|
|
||||||
class="ml-2"
|
|
||||||
>
|
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
|
||||||
</button>
|
|
||||||
<span v-show="showB64Copy">Copied!</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-slate-500 text-sm font-bold">Public Key (hex)</div>
|
|
||||||
<div
|
|
||||||
class="text-sm text-slate-500 flex justify-start items-center mb-1"
|
|
||||||
>
|
|
||||||
<code class="truncate">{{ publicHex }}</code>
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
doCopyTwoSecRedo(publicHex, () => (showPubCopy = !showPubCopy))
|
|
||||||
"
|
|
||||||
class="ml-2"
|
|
||||||
>
|
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
|
||||||
</button>
|
|
||||||
<span v-show="showPubCopy">Copied!</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-slate-500 text-sm font-bold">Derivation Path</div>
|
|
||||||
<div
|
|
||||||
class="text-sm text-slate-500 flex justify-start items-center mb-1"
|
|
||||||
>
|
|
||||||
<code class="truncate">{{ derivationPath }}</code>
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
doCopyTwoSecRedo(
|
|
||||||
derivationPath,
|
|
||||||
() => (showDerCopy = !showDerCopy),
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="ml-2"
|
|
||||||
>
|
|
||||||
<fa icon="copy" class="text-slate-400 fa-fw"></fa>
|
|
||||||
</button>
|
|
||||||
<span v-show="showDerCopy">Copied!</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<label
|
<label
|
||||||
for="toggleShowAmounts"
|
for="toggleShowAmounts"
|
||||||
class="flex items-center cursor-pointer py-2"
|
class="flex items-center cursor-pointer mb-6"
|
||||||
@click="handleChange"
|
@click="handleChange"
|
||||||
>
|
>
|
||||||
<!-- label -->
|
|
||||||
<h2 class="text-slate-500 text-sm font-bold mb-2">
|
|
||||||
Show amounts given with contacts
|
|
||||||
</h2>
|
|
||||||
<!-- toggle -->
|
<!-- toggle -->
|
||||||
<div class="relative ml-2">
|
<div class="relative">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -285,31 +272,21 @@
|
|||||||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- label -->
|
||||||
|
<div class="ml-2">Show amounts given with contacts</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="flex py-2">
|
<div class="flex py-2">
|
||||||
<button class="text-blue-500">
|
|
||||||
<!-- id used by puppeteer test script -->
|
|
||||||
<router-link
|
<router-link
|
||||||
id="switch-identity-link"
|
|
||||||
:to="{ name: 'identity-switcher' }"
|
:to="{ name: 'identity-switcher' }"
|
||||||
class="block text-center"
|
class="block text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
|
||||||
>
|
>
|
||||||
Switch Identity / No Identity
|
Switch Identity / No Identity
|
||||||
</router-link>
|
</router-link>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex py-2">
|
<div class="flex py-2">
|
||||||
<button class="text-blue-500">
|
Claim Server
|
||||||
<router-link :to="{ name: 'statistics' }" class="block text-center">
|
|
||||||
See Achievements & Statistics
|
|
||||||
</router-link>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex py-4">
|
|
||||||
<h2 class="text-slate-500 text-sm font-bold mb-2">Claim Server</h2>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="block w-full rounded border border-slate-400 px-3 py-2"
|
class="block w-full rounded border border-slate-400 px-3 py-2"
|
||||||
@@ -341,6 +318,17 @@
|
|||||||
Use Local
|
Use Local
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button class="text-blue-500">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'statistics' }"
|
||||||
|
class="block text-center py-3"
|
||||||
|
>
|
||||||
|
See Achievements & Statistics
|
||||||
|
</router-link>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -351,11 +339,11 @@ import "dexie-export-import";
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
|
||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { ErrorResponse, RateLimits } from "@/libs/endorserServer";
|
import { ErrorResponse, RateLimits } from "@/libs/endorserServer";
|
||||||
|
|
||||||
@@ -376,6 +364,14 @@ interface IAccount {
|
|||||||
derivationPath: string;
|
derivationPath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SettingsType {
|
||||||
|
activeDid?: string;
|
||||||
|
apiServer?: string;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
showContactGivesInline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({ components: { QuickNav } })
|
@Component({ components: { QuickNav } })
|
||||||
export default class AccountViewView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
@@ -386,8 +382,8 @@ export default class AccountViewView extends Vue {
|
|||||||
apiServer = "";
|
apiServer = "";
|
||||||
apiServerInput = "";
|
apiServerInput = "";
|
||||||
derivationPath = "";
|
derivationPath = "";
|
||||||
givenName = "";
|
firstName = "";
|
||||||
isRegistered = false;
|
lastName = "";
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
publicHex = "";
|
publicHex = "";
|
||||||
publicBase64 = "";
|
publicBase64 = "";
|
||||||
@@ -402,6 +398,8 @@ export default class AccountViewView extends Vue {
|
|||||||
showPubCopy = false;
|
showPubCopy = false;
|
||||||
|
|
||||||
showAdvanced = false;
|
showAdvanced = false;
|
||||||
|
alertMessage = "";
|
||||||
|
alertTitle = "";
|
||||||
|
|
||||||
public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
|
public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
|
||||||
try {
|
try {
|
||||||
@@ -426,7 +424,7 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return parsed identity or null if not found
|
// Return parsed identity or null if not found
|
||||||
return JSON.parse((account?.identity as string) || "null");
|
return JSON.parse(account?.identity || "null");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -507,14 +505,12 @@ export default class AccountViewView extends Vue {
|
|||||||
* Initializes component state with values from the database or defaults.
|
* Initializes component state with values from the database or defaults.
|
||||||
* @param {SettingsType} settings - Object containing settings from the database.
|
* @param {SettingsType} settings - Object containing settings from the database.
|
||||||
*/
|
*/
|
||||||
initializeState(settings: Settings | undefined) {
|
initializeState(settings: SettingsType | undefined) {
|
||||||
this.activeDid = (settings?.activeDid as string) || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.apiServer = (settings?.apiServer as string) || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.apiServerInput = (settings?.apiServer as string) || "";
|
this.apiServerInput = settings?.apiServer || "";
|
||||||
this.givenName =
|
this.firstName = settings?.firstName || "";
|
||||||
(settings?.firstName || "") +
|
this.lastName = settings?.lastName || "";
|
||||||
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
|
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
|
||||||
this.showContactGives = !!settings?.showContactGivesInline;
|
this.showContactGives = !!settings?.showContactGivesInline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,7 +527,7 @@ export default class AccountViewView extends Vue {
|
|||||||
) {
|
) {
|
||||||
this.publicHex = identity.keys[0].publicKeyHex;
|
this.publicHex = identity.keys[0].publicKeyHex;
|
||||||
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
|
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
|
||||||
this.derivationPath = identity.keys[0].meta.derivationPath as string;
|
this.derivationPath = identity.keys[0].meta.derivationPath;
|
||||||
|
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
activeDid: identity.did,
|
activeDid: identity.did,
|
||||||
@@ -701,27 +697,6 @@ export default class AccountViewView extends Vue {
|
|||||||
const resp = await this.fetchRateLimits(identity);
|
const resp = await this.fetchRateLimits(identity);
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
this.limits = resp.data;
|
this.limits = resp.data;
|
||||||
if (!this.isRegistered) {
|
|
||||||
// the user is not known to be registered, but they are so let's record it
|
|
||||||
try {
|
|
||||||
await db.open();
|
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
|
||||||
isRegistered: true,
|
|
||||||
});
|
|
||||||
this.isRegistered = true;
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Got an error updating settings:", err);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "warning",
|
|
||||||
title: "Update Error",
|
|
||||||
text: "Unable to update your settings. Check claim limits again.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleRateLimitsError(error);
|
this.handleRateLimitsError(error);
|
||||||
@@ -750,13 +725,8 @@ export default class AccountViewView extends Vue {
|
|||||||
private handleRateLimitsError(error: unknown) {
|
private handleRateLimitsError(error: unknown) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
const data = error.response?.data as ErrorResponse;
|
const data = error.response?.data as ErrorResponse;
|
||||||
this.limitsMessage =
|
this.limitsMessage = data?.error?.message || "Bad server response.";
|
||||||
(data?.error?.message as string) || "Bad server response.";
|
console.error("Bad response retrieving limits:", error);
|
||||||
console.log(
|
|
||||||
"Got bad response retrieving limits, which usually means user isn't registered. Server says:",
|
|
||||||
this.limitsMessage,
|
|
||||||
//error,
|
|
||||||
);
|
|
||||||
} else if (
|
} else if (
|
||||||
error instanceof Error &&
|
error instanceof Error &&
|
||||||
error.message ===
|
error.message ===
|
||||||
|
|||||||
@@ -16,6 +16,10 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Search -->
|
||||||
|
|
||||||
|
<!-- Initial Loading Animation -->
|
||||||
|
|
||||||
<!-- Results List -->
|
<!-- Results List -->
|
||||||
<ul class="border-t border-slate-300">
|
<ul class="border-t border-slate-300">
|
||||||
<li class="border-b border-slate-300 py-3">
|
<li class="border-b border-slate-300 py-3">
|
||||||
@@ -66,7 +70,12 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<GiftedDialog ref="customDialog" message="Received from"> </GiftedDialog>
|
<GiftedDialog
|
||||||
|
ref="customDialog"
|
||||||
|
@dialog-result="handleDialogResult"
|
||||||
|
message="Received from"
|
||||||
|
>
|
||||||
|
</GiftedDialog>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -74,10 +83,16 @@
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { Account, AccountsSchema } from "@/db/tables/accounts";
|
import { AccountsSchema } from "@/db/tables/accounts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import { GiverInputInfo } from "@/libs/endorserServer";
|
import {
|
||||||
|
createAndSubmitGive,
|
||||||
|
CreateAndSubmitGiveResult,
|
||||||
|
ErrorResult,
|
||||||
|
GiverInputInfo,
|
||||||
|
GiverOutputInfo,
|
||||||
|
} from "@/libs/endorserServer";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
@@ -109,10 +124,10 @@ export default class ContactGiftingView extends Vue {
|
|||||||
|
|
||||||
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
|
||||||
.where("did")
|
.where("did")
|
||||||
.equals(activeDid)
|
.equals(activeDid)
|
||||||
.first()) as Account;
|
.first();
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
|
|
||||||
if (!identity) {
|
if (!identity) {
|
||||||
@@ -135,7 +150,7 @@ export default class ContactGiftingView extends Vue {
|
|||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await db.contacts.toArray();
|
||||||
@@ -158,5 +173,123 @@ export default class ContactGiftingView extends Vue {
|
|||||||
openDialog(giver: GiverInputInfo) {
|
openDialog(giver: GiverInputInfo) {
|
||||||
(this.$refs.customDialog as GiftedDialog).open(giver);
|
(this.$refs.customDialog as GiftedDialog).open(giver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDialogResult(result: GiverOutputInfo) {
|
||||||
|
if (result.action === "confirm") {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.recordGive(
|
||||||
|
result.giver?.did,
|
||||||
|
result.description,
|
||||||
|
result.hours,
|
||||||
|
).then(() => {
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// action was "cancel" so do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param giverDid may be null
|
||||||
|
* @param description may be an empty string
|
||||||
|
* @param hours may be 0
|
||||||
|
*/
|
||||||
|
public async recordGive(
|
||||||
|
giverDid?: string,
|
||||||
|
description?: string,
|
||||||
|
hours?: number,
|
||||||
|
) {
|
||||||
|
if (!this.activeDid) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "You must select an identity before you can record a give.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!description && !hours) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "You must enter a description or some number of hours.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
|
const result = await createAndSubmitGive(
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
identity,
|
||||||
|
giverDid,
|
||||||
|
this.activeDid,
|
||||||
|
description,
|
||||||
|
hours,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.isGiveCreationError(result)) {
|
||||||
|
const errorMessage = this.getGiveCreationErrorMessage(result);
|
||||||
|
console.log("Error with give result:", result);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: errorMessage || "There was an error recording the give.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Success",
|
||||||
|
text: "That gift was recorded.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log("Error with give caught:", error);
|
||||||
|
|
||||||
|
const message =
|
||||||
|
error.userMessage ||
|
||||||
|
error.response?.data?.error?.message ||
|
||||||
|
"There was an error recording the Give.";
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: message,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions for readability
|
||||||
|
|
||||||
|
isGiveCreationError(result: CreateAndSubmitGiveResult) {
|
||||||
|
return result.type == "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
getGiveCreationErrorMessage(result: CreateAndSubmitGiveResult) {
|
||||||
|
return (result as ErrorResult).error?.userMessage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="p-6 pb-24">
|
<section id="Content" class="p-6 pb-24">
|
||||||
<!-- Heading -->
|
<!-- Heading -->
|
||||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4">
|
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
||||||
Your Contact Info
|
Your Contact Info
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -17,17 +17,12 @@
|
|||||||
:dotsOptions="{ type: 'square' }"
|
:dotsOptions="{ type: 'square' }"
|
||||||
class="flex justify-center"
|
class="flex justify-center"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h1 class="text-4xl text-center font-light pt-4">Scan Contact Info</h1>
|
|
||||||
<qrcode-stream @detect="onScanDetect" @error="onScanError" />
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import QRCodeVue3 from "qr-code-generator-vue3";
|
import QRCodeVue3 from "qr-code-generator-vue3";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { QrcodeStream } from "vue-qrcode-reader";
|
|
||||||
|
|
||||||
import { accountsDB, 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 * as R from "ramda";
|
import * as R from "ramda";
|
||||||
@@ -35,10 +30,6 @@ import { SimpleSigner } from "@/libs/crypto";
|
|||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
import {
|
|
||||||
CONTACT_URL_PREFIX,
|
|
||||||
ENDORSER_JWT_URL_LOCATION,
|
|
||||||
} from "@/libs/endorserServer";
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const Buffer = require("buffer/").Buffer;
|
const Buffer = require("buffer/").Buffer;
|
||||||
@@ -52,7 +43,6 @@ interface Notification {
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
QrcodeStream,
|
|
||||||
QRCodeVue3,
|
QRCodeVue3,
|
||||||
QuickNav,
|
QuickNav,
|
||||||
},
|
},
|
||||||
@@ -108,9 +98,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
iat: Date.now(),
|
iat: Date.now(),
|
||||||
iss: this.activeDid,
|
iss: this.activeDid,
|
||||||
own: {
|
own: {
|
||||||
name:
|
name: (settings?.firstName || "") + " " + (settings?.lastName || ""),
|
||||||
(settings?.firstName || "") +
|
|
||||||
(settings?.lastName ? ` ${settings.lastName}` : ""), // deprecated, pre v 0.1.3
|
|
||||||
publicEncKey,
|
publicEncKey,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -124,47 +112,9 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
issuer: identity.did,
|
issuer: identity.did,
|
||||||
signer: signer,
|
signer: signer,
|
||||||
});
|
});
|
||||||
const viewPrefix = CONTACT_URL_PREFIX + ENDORSER_JWT_URL_LOCATION;
|
const viewPrefix = "https://endorser.ch/contact?jwt=";
|
||||||
this.qrValue = viewPrefix + vcJwt;
|
this.qrValue = viewPrefix + vcJwt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param content is the result of a QR scan, an array with one item with a rawValue property
|
|
||||||
*/
|
|
||||||
// Unfortunately, there are not typescript definitions for the qrcode-stream component yet.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
onScanDetect(content: any) {
|
|
||||||
if (content[0]?.rawValue) {
|
|
||||||
console.log("onDetect", content[0].rawValue);
|
|
||||||
localStorage.setItem("contactEndorserUrl", content[0].rawValue);
|
|
||||||
this.$router.push({ name: "contacts" });
|
|
||||||
} else {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "warning",
|
|
||||||
title: "Invalid Contact QR Code",
|
|
||||||
text: "No QR code detected with contact information.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
onScanError(error: any) {
|
|
||||||
console.log("Scan was invalid:", error);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "warning",
|
|
||||||
title: "Invalid Scan",
|
|
||||||
text: "The scan was invalid.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -20,11 +20,6 @@
|
|||||||
|
|
||||||
<!-- New Contact -->
|
<!-- New Contact -->
|
||||||
<div class="mb-4 flex">
|
<div class="mb-4 flex">
|
||||||
<span class="self-center bg-slate-500 text-white px-1.5 py-1 rounded-md">
|
|
||||||
<router-link :to="{ name: 'contact-qr' }">
|
|
||||||
<fa icon="qrcode" class="fa-fw" />
|
|
||||||
</router-link>
|
|
||||||
</span>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="DID, Name, Public Key"
|
placeholder="DID, Name, Public Key"
|
||||||
@@ -93,16 +88,6 @@
|
|||||||
class="inline-block align-text-bottom border border-slate-300 rounded"
|
class="inline-block align-text-bottom border border-slate-300 rounded"
|
||||||
></EntityIcon>
|
></EntityIcon>
|
||||||
{{ contact.name || "(no name)" }}
|
{{ contact.name || "(no name)" }}
|
||||||
<button
|
|
||||||
class="text-sm uppercase bg-slate-500 text-white px-1 rounded-md"
|
|
||||||
@click="
|
|
||||||
contactEdit = contact;
|
|
||||||
contactNewName = contact.name;
|
|
||||||
"
|
|
||||||
title="Edit"
|
|
||||||
>
|
|
||||||
<fa icon="pen" class="fa-fw" />
|
|
||||||
</button>
|
|
||||||
</h2>
|
</h2>
|
||||||
<div class="text-sm truncate">{{ contact.did }}</div>
|
<div class="text-sm truncate">{{ contact.did }}</div>
|
||||||
<div class="text-sm truncate" v-if="contact.publicKeyBase64">
|
<div class="text-sm truncate" v-if="contact.publicKeyBase64">
|
||||||
@@ -219,31 +204,6 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p v-else>This identity has no contacts.</p>
|
<p v-else>This identity has no contacts.</p>
|
||||||
|
|
||||||
<div v-if="contactEdit !== null" class="dialog-overlay">
|
|
||||||
<div class="dialog">
|
|
||||||
<h1 class="text-xl font-bold text-center mb-4">Edit Name</h1>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="block w-full rounded border border-slate-400 mb-2 px-3 py-2"
|
|
||||||
placeholder="Name"
|
|
||||||
v-model="contactNewName"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded -ml-1.5 border-l border-blue-400"
|
|
||||||
@click="onClickSaveName(contactEdit, contactNewName)"
|
|
||||||
>
|
|
||||||
<fa icon="save" />
|
|
||||||
</button>
|
|
||||||
<span class="inline-block w-2" />
|
|
||||||
<button
|
|
||||||
class="text-sm bg-blue-600 text-white px-2 py-1.5 rounded -ml-1.5 border-l border-blue-400"
|
|
||||||
@click="onClickCancelName()"
|
|
||||||
>
|
|
||||||
<fa icon="ban" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -251,17 +211,11 @@
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
|
|
||||||
import { NotificationIface } from "@/constants/app";
|
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import {
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
accessToken,
|
|
||||||
getContactPayloadFromJwtUrl,
|
|
||||||
SimpleSigner,
|
|
||||||
} from "@/libs/crypto";
|
|
||||||
import {
|
import {
|
||||||
GiveServerRecord,
|
GiveServerRecord,
|
||||||
GiveVerifiableCredential,
|
GiveVerifiableCredential,
|
||||||
@@ -271,24 +225,27 @@ import {
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import { Account } from "@/db/tables/accounts";
|
|
||||||
|
|
||||||
// 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;
|
||||||
|
|
||||||
|
interface Notification {
|
||||||
|
group: string;
|
||||||
|
type: string;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { QuickNav, EntityIcon },
|
components: { QuickNav, EntityIcon },
|
||||||
})
|
})
|
||||||
export default class ContactsView extends Vue {
|
export default class ContactsView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
contacts: Array<Contact> = [];
|
contacts: Array<Contact> = [];
|
||||||
contactEndorserUrl = localStorage.getItem("contactEndorserUrl") || "";
|
|
||||||
contactInput = "";
|
contactInput = "";
|
||||||
contactEdit: Contact | null = null;
|
|
||||||
contactNewName = "";
|
|
||||||
// { "did:...": concatenated-descriptions } entry for each contact
|
// { "did:...": concatenated-descriptions } entry for each contact
|
||||||
givenByMeDescriptions: Record<string, string> = {};
|
givenByMeDescriptions: Record<string, string> = {};
|
||||||
// { "did:...": amount } entry for each contact
|
// { "did:...": amount } entry for each contact
|
||||||
@@ -309,7 +266,7 @@ export default class ContactsView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
|
|
||||||
@@ -322,18 +279,12 @@ export default class ContactsView extends Vue {
|
|||||||
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
||||||
allContacts,
|
allContacts,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.contactEndorserUrl) {
|
|
||||||
await this.newContactFromScan(this.contactEndorserUrl);
|
|
||||||
localStorage.removeItem("contactEndorserUrl");
|
|
||||||
this.contactEndorserUrl = "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getIdentity(activeDid: string) {
|
public async getIdentity(activeDid: string) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
const account = R.find((acc) => acc.did === activeDid, accounts) as Account;
|
const account = R.find((acc) => acc.did === activeDid, accounts);
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
|
|
||||||
if (!identity) {
|
if (!identity) {
|
||||||
@@ -395,7 +346,7 @@ export default class ContactsView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Server Error",
|
title: "Error With Server",
|
||||||
text:
|
text:
|
||||||
"Got an error retrieving your " +
|
"Got an error retrieving your " +
|
||||||
(useRecipient ? "given" : "received") +
|
(useRecipient ? "given" : "received") +
|
||||||
@@ -454,7 +405,7 @@ export default class ContactsView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Server Error",
|
title: "Error With Server",
|
||||||
text: error as string,
|
text: error as string,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
@@ -463,18 +414,6 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onClickNewContact(): Promise<void> {
|
async onClickNewContact(): Promise<void> {
|
||||||
if (!this.contactInput) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "warning",
|
|
||||||
title: "No Contact",
|
|
||||||
text: "There was no contact info to add.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let did = this.contactInput;
|
let did = this.contactInput;
|
||||||
let name, publicKeyBase64;
|
let name, publicKeyBase64;
|
||||||
const commaPos1 = this.contactInput.indexOf(",");
|
const commaPos1 = this.contactInput.indexOf(",");
|
||||||
@@ -493,74 +432,12 @@ export default class ContactsView extends Vue {
|
|||||||
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
|
publicKeyBase64 = Buffer.from(publicKeyBase64, "hex").toString("base64");
|
||||||
}
|
}
|
||||||
const newContact = { did, name, publicKeyBase64 };
|
const newContact = { did, name, publicKeyBase64 };
|
||||||
return this.addContact(newContact);
|
await db.contacts.add(newContact);
|
||||||
}
|
|
||||||
|
|
||||||
async newContactFromScan(url: string): Promise<void> {
|
|
||||||
const payload = getContactPayloadFromJwtUrl(url);
|
|
||||||
if (!payload) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "No Contact Info",
|
|
||||||
text: "The contact info could not be parsed.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
return this.addContact({
|
|
||||||
did: payload.iss,
|
|
||||||
name: payload.own.name,
|
|
||||||
publicKeyBase64: payload.own.publicEncKey,
|
|
||||||
} as Contact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async addContact(newContact: Contact) {
|
|
||||||
if (!newContact.did) {
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Incomplete Contact",
|
|
||||||
text: "Cannot add a contact without a DID.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return db.contacts
|
|
||||||
.add(newContact)
|
|
||||||
.then(() => {
|
|
||||||
const allContacts = this.contacts.concat([newContact]);
|
const allContacts = this.contacts.concat([newContact]);
|
||||||
this.contacts = R.sort(
|
this.contacts = R.sort(
|
||||||
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
(a: Contact, b) => (a.name || "").localeCompare(b.name || ""),
|
||||||
allContacts,
|
allContacts,
|
||||||
);
|
);
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "success",
|
|
||||||
title: "Contact added",
|
|
||||||
text: newContact.name + " was added.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Error when adding contact to storage:", err);
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Contact Not Added",
|
|
||||||
text: "An error prevented importing.",
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteContact(contact: Contact) {
|
async deleteContact(contact: Contact) {
|
||||||
@@ -590,16 +467,6 @@ export default class ContactsView extends Vue {
|
|||||||
"?",
|
"?",
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "toast",
|
|
||||||
text: "",
|
|
||||||
title: "Registration submitted...",
|
|
||||||
},
|
|
||||||
1000,
|
|
||||||
);
|
|
||||||
|
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
|
|
||||||
const vcClaim: RegisterVerifiableCredential = {
|
const vcClaim: RegisterVerifiableCredential = {
|
||||||
@@ -682,7 +549,7 @@ export default class ContactsView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Server Error",
|
title: "Error With Server",
|
||||||
text: userMessage,
|
text: userMessage,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
@@ -707,30 +574,36 @@ export default class ContactsView extends Vue {
|
|||||||
contact.seesMe = visibility;
|
contact.seesMe = visibility;
|
||||||
db.contacts.update(contact.did, { seesMe: visibility });
|
db.contacts.update(contact.did, { seesMe: visibility });
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error("Bad response setting visibility: ", resp.data);
|
||||||
"Got some bad server response when setting visibility: ",
|
if (resp.data.error?.message) {
|
||||||
resp,
|
|
||||||
);
|
|
||||||
const message =
|
|
||||||
resp.data.error?.message || "Bad server response of " + resp.status;
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Server Error",
|
title: "Error With Server",
|
||||||
text: message,
|
text: resp.data.error?.message,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error With Server",
|
||||||
|
text: "Bad server response of " + resp.status,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Got some server error when setting visibility:", err);
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Server Error",
|
title: "Error With Server",
|
||||||
text: "Check connectivity and try again.",
|
text: err as string,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -755,7 +628,7 @@ export default class ContactsView extends Vue {
|
|||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "info",
|
type: "toast",
|
||||||
title: "Refreshed",
|
title: "Refreshed",
|
||||||
text:
|
text:
|
||||||
this.nameForContact(contact, true) +
|
this.nameForContact(contact, true) +
|
||||||
@@ -763,29 +636,38 @@ export default class ContactsView extends Vue {
|
|||||||
(visibility ? "" : "not ") +
|
(visibility ? "" : "not ") +
|
||||||
"see your activity.",
|
"see your activity.",
|
||||||
},
|
},
|
||||||
-1,
|
5000,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log("Got bad server response when checking visibility: ", resp);
|
if (resp.data.error?.message) {
|
||||||
const message = resp.data.error?.message || "Got bad server response.";
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Server Error",
|
title: "Error With Server",
|
||||||
text: message,
|
text: resp.data.error?.message,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error With Server",
|
||||||
|
text: "Bad server response of " + resp.status,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Caught error from server request to check visibility:", err);
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Server Error",
|
title: "Error With Server",
|
||||||
text: "Check connectivity and try again.",
|
text: err as string,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
@@ -985,7 +867,7 @@ export default class ContactsView extends Vue {
|
|||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Server Error",
|
title: "Error With Server",
|
||||||
text: userMessage,
|
text: userMessage,
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
@@ -994,18 +876,6 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onClickCancelName() {
|
|
||||||
this.contactEdit = null;
|
|
||||||
this.contactNewName = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private async onClickSaveName(contact: Contact, newName: string) {
|
|
||||||
contact.name = newName;
|
|
||||||
return db.contacts
|
|
||||||
.update(contact.did, { name: newName })
|
|
||||||
.then(() => (this.contactEdit = null));
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggleShowGiveTotals() {
|
public toggleShowGiveTotals() {
|
||||||
if (this.showGiveTotals) {
|
if (this.showGiveTotals) {
|
||||||
this.showGiveTotals = false;
|
this.showGiveTotals = false;
|
||||||
@@ -1030,26 +900,6 @@ export default class ContactsView extends Vue {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.dialog-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
.dialog {
|
|
||||||
background-color: white;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tooltip from https://www.w3schools.com/css/css_tooltip.asp */
|
/* Tooltip from https://www.w3schools.com/css/css_tooltip.asp */
|
||||||
/* Tooltip container */
|
/* Tooltip container */
|
||||||
.tooltip {
|
.tooltip {
|
||||||
@@ -1057,6 +907,7 @@ export default class ContactsView extends Vue {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
|
border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tooltip text */
|
/* Tooltip text */
|
||||||
.tooltip .tooltiptext {
|
.tooltip .tooltiptext {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|||||||
@@ -41,15 +41,14 @@
|
|||||||
You need someone to register you -- usually the person who told you
|
You need someone to register you -- usually the person who told you
|
||||||
about this app, on the Contacts
|
about this app, on the Contacts
|
||||||
<fa icon="circle-user" class="fa-fw" /> page. After they register you,
|
<fa icon="circle-user" class="fa-fw" /> page. After they register you,
|
||||||
you can select any contact on the home page (or "anonymous") and record
|
and after you have contacts, you can select any contact on the home page
|
||||||
your appreciation for... whatever. The main goal is to record what
|
and record your appreciation for... whatever. That is a claim recorded
|
||||||
people have given you, to grow gifting economies. Each claim is recorded
|
|
||||||
on a custom ledger. The day after being registered, you'll be able to
|
on a custom ledger. The day after being registered, you'll be able to
|
||||||
able to register others; later, you can create projects, too.
|
register others; later, you can create projects, too.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Note that there are limits to how many others each person can register,
|
Note that there are limits to how many each person can register, so you
|
||||||
so you may have to wait.
|
may have to wait.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 class="text-xl font-semibold">How do I backup all my data?</h2>
|
<h2 class="text-xl font-semibold">How do I backup all my data?</h2>
|
||||||
@@ -131,9 +130,7 @@
|
|||||||
|
|
||||||
<h2 class="text-xl font-semibold">How do I create another identity?</h2>
|
<h2 class="text-xl font-semibold">How do I create another identity?</h2>
|
||||||
<p>
|
<p>
|
||||||
Before doing this, note that it is an advanced feature that affects
|
Go
|
||||||
functionality (eg. the words "Alt ID" next to results, backup features)
|
|
||||||
so beware if you think that may cause confusion. You can
|
|
||||||
<router-link to="start" class="text-blue-500">
|
<router-link to="start" class="text-blue-500">
|
||||||
create another identity here.
|
create another identity here.
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|||||||
@@ -6,30 +6,142 @@
|
|||||||
Time Safari
|
Time Safari
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<!-- show the actions for recognizing a give -->
|
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div v-if="!activeDid">
|
<h2 class="text-xl font-bold mb-4">Notiwind Alert Test Suite</h2>
|
||||||
To record others' giving,
|
|
||||||
<router-link :to="{ name: 'start' }" class="text-blue-500">
|
<button
|
||||||
create your identifier.</router-link
|
@click="
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: 'alert',
|
||||||
|
type: 'toast',
|
||||||
|
text: 'I\'m a toast. Don\'t mind me.',
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="font-bold uppercase bg-slate-400 text-white px-3 py-2 rounded-md mr-2"
|
||||||
>
|
>
|
||||||
|
Toast (self-dismiss)
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: 'alert',
|
||||||
|
type: 'info',
|
||||||
|
title: 'Information Alert',
|
||||||
|
text: 'Just wanted you to know.',
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
>
|
||||||
|
Info
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: 'alert',
|
||||||
|
type: 'success',
|
||||||
|
title: 'Success Alert',
|
||||||
|
text: 'Congratulations!',
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="font-bold uppercase bg-emerald-600 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
>
|
||||||
|
Success
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: 'alert',
|
||||||
|
type: 'warning',
|
||||||
|
title: 'Warning Alert',
|
||||||
|
text: 'You might wanna look at this.',
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="font-bold uppercase bg-amber-600 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
>
|
||||||
|
Warning
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: 'alert',
|
||||||
|
type: 'danger',
|
||||||
|
title: 'Danger Alert',
|
||||||
|
text: 'Something terrible has happened!',
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="font-bold uppercase bg-rose-600 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
>
|
||||||
|
Danger
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: 'modal',
|
||||||
|
type: 'notification-permission',
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
>
|
||||||
|
Notif ON
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: 'modal',
|
||||||
|
type: 'notification-mute',
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
>
|
||||||
|
Notif MUTE
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: 'modal',
|
||||||
|
type: 'notification-off',
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
||||||
|
>
|
||||||
|
Notif OFF
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="!isRegistered">
|
<div class="mb-8">
|
||||||
To record others' giving, someone must register your account, so show
|
<h2 class="text-xl font-bold">Quick Action</h2>
|
||||||
them
|
<p class="mb-4">Record a gift from a contact:</p>
|
||||||
<router-link :to="{ name: 'contact-qr' }" class="text-blue-500">
|
|
||||||
your identity info</router-link
|
|
||||||
>
|
|
||||||
and then
|
|
||||||
<router-link :to="{ name: 'account' }" class="text-blue-500">
|
|
||||||
check your limits.</router-link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<!-- activeDid && isRegistered -->
|
|
||||||
<h2 class="text-xl font-bold">Record a Gift</h2>
|
|
||||||
|
|
||||||
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
<ul class="grid grid-cols-4 gap-x-3 gap-y-5 text-center mb-5">
|
||||||
<li @click="openDialog()">
|
<li @click="openDialog()">
|
||||||
@@ -41,7 +153,7 @@
|
|||||||
<h3
|
<h3
|
||||||
class="text-xs italic font-medium text-ellipsis whitespace-nowrap overflow-hidden"
|
class="text-xs italic font-medium text-ellipsis whitespace-nowrap overflow-hidden"
|
||||||
>
|
>
|
||||||
Anonymous/Unnamed
|
Anonymous
|
||||||
</h3>
|
</h3>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
@@ -64,7 +176,7 @@
|
|||||||
|
|
||||||
<!-- Ideally, this button should only be visible when the active account has more than 7 or 11 contacts in their list (we want to limit the grid count above to 8 or 12 accounts to keep it compact) -->
|
<!-- Ideally, this button should only be visible when the active account has more than 7 or 11 contacts in their list (we want to limit the grid count above to 8 or 12 accounts to keep it compact) -->
|
||||||
<router-link
|
<router-link
|
||||||
v-if="allContacts.length >= 7"
|
v-if="allContacts.length > 7"
|
||||||
:to="{ name: 'contact-gives' }"
|
:to="{ name: 'contact-gives' }"
|
||||||
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
|
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
|
||||||
>
|
>
|
||||||
@@ -79,9 +191,13 @@
|
|||||||
(No contacts to show.)
|
(No contacts to show.)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<GiftedDialog ref="customDialog" message="Received from"> </GiftedDialog>
|
<GiftedDialog
|
||||||
|
ref="customDialog"
|
||||||
|
@dialog-result="handleDialogResult"
|
||||||
|
message="Received from"
|
||||||
|
>
|
||||||
|
</GiftedDialog>
|
||||||
|
|
||||||
<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 class="text-xl font-bold mb-4">Latest Activity</h2>
|
<h2 class="text-xl font-bold mb-4">Latest Activity</h2>
|
||||||
@@ -117,18 +233,19 @@
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import {
|
import {
|
||||||
|
createAndSubmitGive,
|
||||||
didInfo,
|
didInfo,
|
||||||
GiverInputInfo,
|
GiverInputInfo,
|
||||||
|
GiverOutputInfo,
|
||||||
GiveServerRecord,
|
GiveServerRecord,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { Account } from "@/db/tables/accounts";
|
|
||||||
|
|
||||||
interface Notification {
|
interface Notification {
|
||||||
group: string;
|
group: string;
|
||||||
@@ -152,7 +269,6 @@ export default class HomeView extends Vue {
|
|||||||
feedPreviousOldestId?: string;
|
feedPreviousOldestId?: string;
|
||||||
feedLastViewedId?: string;
|
feedLastViewedId?: string;
|
||||||
isHiddenSpinner = true;
|
isHiddenSpinner = true;
|
||||||
isRegistered = false;
|
|
||||||
numAccounts = 0;
|
numAccounts = 0;
|
||||||
|
|
||||||
async beforeCreate() {
|
async beforeCreate() {
|
||||||
@@ -162,10 +278,10 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
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
|
||||||
.where("did")
|
.where("did")
|
||||||
.equals(activeDid)
|
.equals(activeDid)
|
||||||
.first()) as Account;
|
.first();
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
|
|
||||||
if (!identity) {
|
if (!identity) {
|
||||||
@@ -192,12 +308,11 @@ export default class HomeView extends Vue {
|
|||||||
this.allMyDids = allAccounts.map((acc) => acc.did);
|
this.allMyDids = allAccounts.map((acc) => acc.did);
|
||||||
|
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await db.contacts.toArray();
|
||||||
this.feedLastViewedId = settings?.lastViewedClaimId;
|
this.feedLastViewedId = settings?.lastViewedClaimId;
|
||||||
this.isRegistered = !!settings?.isRegistered;
|
|
||||||
this.updateAllFeed();
|
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) {
|
||||||
@@ -223,9 +338,7 @@ export default class HomeView extends Vue {
|
|||||||
if (this.activeDid) {
|
if (this.activeDid) {
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const allAccounts = await accountsDB.accounts.toArray();
|
const allAccounts = await accountsDB.accounts.toArray();
|
||||||
const account = allAccounts.find(
|
const account = allAccounts.find((acc) => acc.did === this.activeDid);
|
||||||
(acc) => acc.did === this.activeDid,
|
|
||||||
) as Account;
|
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
|
|
||||||
if (!identity) {
|
if (!identity) {
|
||||||
@@ -302,7 +415,6 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
giveDescription(giveRecord: GiveServerRecord) {
|
giveDescription(giveRecord: GiveServerRecord) {
|
||||||
// similar code is in endorser-mobile utility.ts
|
|
||||||
// claim.claim happen for some claims wrapped in a Verifiable Credential
|
// claim.claim happen for some claims wrapped in a Verifiable Credential
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const claim = (giveRecord.fullClaim as any).claim || giveRecord.fullClaim;
|
const claim = (giveRecord.fullClaim as any).claim || giveRecord.fullClaim;
|
||||||
@@ -315,18 +427,9 @@ export default class HomeView extends Vue {
|
|||||||
this.allMyDids,
|
this.allMyDids,
|
||||||
this.allContacts,
|
this.allContacts,
|
||||||
);
|
);
|
||||||
let gaveAmount = claim.object?.amountOfThisGood
|
const gaveAmount = claim.object?.amountOfThisGood
|
||||||
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
|
? this.displayAmount(claim.object.unitCode, claim.object.amountOfThisGood)
|
||||||
: "";
|
: claim.description || "something unknown";
|
||||||
if (claim.description) {
|
|
||||||
if (gaveAmount) {
|
|
||||||
gaveAmount = gaveAmount + ", and also: ";
|
|
||||||
}
|
|
||||||
gaveAmount = gaveAmount + claim.description;
|
|
||||||
}
|
|
||||||
if (!gaveAmount) {
|
|
||||||
gaveAmount = "something not described";
|
|
||||||
}
|
|
||||||
// recipient.did is for legacy data, before March 2023
|
// recipient.did is for legacy data, before March 2023
|
||||||
const gaveRecipientId =
|
const gaveRecipientId =
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -340,7 +443,7 @@ export default class HomeView extends Vue {
|
|||||||
this.allContacts,
|
this.allContacts,
|
||||||
)
|
)
|
||||||
: "";
|
: "";
|
||||||
return giverInfo + " gave" + gaveRecipientInfo + ": " + gaveAmount;
|
return giverInfo + " gave " + gaveAmount + gaveRecipientInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAmount(code: string, amt: number) {
|
displayAmount(code: string, amt: number) {
|
||||||
@@ -354,5 +457,124 @@ export default class HomeView extends Vue {
|
|||||||
openDialog(giver: GiverInputInfo) {
|
openDialog(giver: GiverInputInfo) {
|
||||||
(this.$refs.customDialog as GiftedDialog).open(giver);
|
(this.$refs.customDialog as GiftedDialog).open(giver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDialogResult(result: GiverOutputInfo) {
|
||||||
|
if (result.action === "confirm") {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.recordGive(
|
||||||
|
result.giver?.did,
|
||||||
|
result.description,
|
||||||
|
result.hours,
|
||||||
|
).then(() => {
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// action was "cancel" so do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param giverDid may be null
|
||||||
|
* @param description may be an empty string
|
||||||
|
* @param hours may be 0
|
||||||
|
*/
|
||||||
|
public async recordGive(
|
||||||
|
giverDid?: string,
|
||||||
|
description?: string,
|
||||||
|
hours?: number,
|
||||||
|
) {
|
||||||
|
if (!this.activeDid) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "You must select an identity before you can record a give.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!description && !hours) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "You must enter a description or some number of hours.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
|
const result = await createAndSubmitGive(
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
identity,
|
||||||
|
giverDid,
|
||||||
|
this.activeDid,
|
||||||
|
description,
|
||||||
|
hours,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.isGiveCreationError(result)) {
|
||||||
|
const errorMessage = this.getGiveCreationErrorMessage(result);
|
||||||
|
console.log("Error with give result:", result);
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: errorMessage || "There was an error recording the give.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Success",
|
||||||
|
text: "That gift was recorded.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (error: any) {
|
||||||
|
console.log("Error with give caught:", error);
|
||||||
|
const message =
|
||||||
|
error.userMessage ||
|
||||||
|
error.response?.data?.error?.message ||
|
||||||
|
"There was an error recording the give.";
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: message,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions for readability
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
isGiveCreationError(result: any) {
|
||||||
|
return result.status !== 201 || result.data?.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
getGiveCreationErrorMessage(result: any) {
|
||||||
|
return result.data?.error?.message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<fa icon="circle-check" class="fa-fw text-blue-600 text-xl mr-3"></fa>
|
<fa icon="circle-check" class="fa-fw text-blue-600 text-xl mr-3"></fa>
|
||||||
<span class="overflow-hidden">
|
<span class="overflow-hidden">
|
||||||
<h2 class="text-xl font-semibold mb-0">
|
<h2 class="text-xl font-semibold mb-0">
|
||||||
{{ givenName }}
|
{{ firstName }} {{ lastName }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="text-sm text-slate-500 truncate">
|
<div class="text-sm text-slate-500 truncate">
|
||||||
<b>ID:</b> <code>{{ activeDid }}</code>
|
<b>ID:</b> <code>{{ activeDid }}</code>
|
||||||
@@ -49,9 +49,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<!-- id used by puppeteer test script -->
|
|
||||||
<router-link
|
<router-link
|
||||||
id="start-link"
|
|
||||||
:to="{ name: 'start' }"
|
:to="{ name: 'start' }"
|
||||||
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
||||||
>
|
>
|
||||||
@@ -71,7 +69,7 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
import { AppString } from "@/constants/app";
|
import { AppString } from "@/constants/app";
|
||||||
import { db, accountsDB } from "@/db/index";
|
import { db, accountsDB } from "@/db/index";
|
||||||
import { AccountsSchema } from "@/db/tables/accounts";
|
import { AccountsSchema } from "@/db/tables/accounts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
|
||||||
interface Notification {
|
interface Notification {
|
||||||
@@ -90,7 +88,8 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
public activeDid = "";
|
public activeDid = "";
|
||||||
public apiServer = "";
|
public apiServer = "";
|
||||||
public apiServerInput = "";
|
public apiServerInput = "";
|
||||||
public givenName = "";
|
public firstName = "";
|
||||||
|
public lastName = "";
|
||||||
public otherIdentities: Array<{ did: string }> = [];
|
public otherIdentities: Array<{ did: string }> = [];
|
||||||
public showContactGives = false;
|
public showContactGives = false;
|
||||||
|
|
||||||
@@ -100,20 +99,19 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
.where("did")
|
.where("did")
|
||||||
.equals(activeDid)
|
.equals(activeDid)
|
||||||
.first();
|
.first();
|
||||||
const identity = JSON.parse((account?.identity as string) || "null");
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
return identity;
|
return identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.apiServerInput = settings?.apiServer || "";
|
this.apiServerInput = settings?.apiServer || "";
|
||||||
this.givenName =
|
this.firstName = settings?.firstName || "No";
|
||||||
(settings?.firstName || "") +
|
this.lastName = settings?.lastName || "Name";
|
||||||
(settings?.lastName ? ` ${settings.lastName}` : ""); // deprecated, pre v 0.1.3
|
|
||||||
this.showContactGives = !!settings?.showContactGivesInline;
|
this.showContactGives = !!settings?.showContactGivesInline;
|
||||||
|
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
@@ -151,7 +149,7 @@ export default class IdentitySwitcherView extends Vue {
|
|||||||
did = undefined;
|
did = undefined;
|
||||||
}
|
}
|
||||||
await db.open();
|
await db.open();
|
||||||
await db.settings.update(MASTER_SETTINGS_KEY, {
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
activeDid: did,
|
activeDid: did,
|
||||||
});
|
});
|
||||||
this.activeDid = did || "";
|
this.activeDid = did || "";
|
||||||
|
|||||||
@@ -17,9 +17,7 @@
|
|||||||
<p class="text-center text-xl mb-4 font-light">
|
<p class="text-center text-xl mb-4 font-light">
|
||||||
Enter your seed phrase below to import your identity on this device.
|
Enter your seed phrase below to import your identity on this device.
|
||||||
</p>
|
</p>
|
||||||
<!-- id used by puppeteer test script -->
|
|
||||||
<input
|
<input
|
||||||
id="seed-input"
|
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Seed Phrase"
|
placeholder="Seed Phrase"
|
||||||
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-4 px-3 py-2"
|
||||||
|
|||||||
@@ -10,15 +10,21 @@
|
|||||||
>
|
>
|
||||||
<fa icon="chevron-left" class="fa-fw"></fa>
|
<fa icon="chevron-left" class="fa-fw"></fa>
|
||||||
</button>
|
</button>
|
||||||
Edit Identity
|
[New/Edit] Identity
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Name"
|
placeholder="First Name"
|
||||||
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-4 px-3 py-2"
|
||||||
v-model="givenName"
|
v-model="firstName"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Last Name"
|
||||||
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
||||||
|
v-model="lastName"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
@@ -44,30 +50,36 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { db } from "@/db/index";
|
import { db } from "@/db/index";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {},
|
components: {},
|
||||||
})
|
})
|
||||||
export default class NewEditAccountView extends Vue {
|
export default class NewEditAccountView extends Vue {
|
||||||
givenName = "";
|
firstName =
|
||||||
|
localStorage.getItem("firstName") === null
|
||||||
|
? "--"
|
||||||
|
: localStorage.getItem("firstName");
|
||||||
|
lastName =
|
||||||
|
localStorage.getItem("lastName") === null
|
||||||
|
? "--"
|
||||||
|
: localStorage.getItem("lastName");
|
||||||
|
|
||||||
// 'created' hook runs when the Vue instance is first created
|
// 'created' hook runs when the Vue instance is first created
|
||||||
async created() {
|
async created() {
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
this.givenName =
|
this.firstName = settings?.firstName || "";
|
||||||
(settings?.firstName || "") +
|
this.lastName = settings?.lastName || "";
|
||||||
(settings?.lastName ? ` ${settings.lastName}` : ""); // deprecated, pre v 0.1.3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSaveChanges() {
|
onClickSaveChanges() {
|
||||||
db.settings.update(MASTER_SETTINGS_KEY, {
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
firstName: this.givenName,
|
firstName: this.firstName,
|
||||||
lastName: "", // deprecated, pre v 0.1.3
|
lastName: this.lastName,
|
||||||
});
|
});
|
||||||
localStorage.setItem("firstName", this.givenName as string);
|
localStorage.setItem("firstName", this.firstName as string);
|
||||||
localStorage.setItem("lastName", ""); // deprecated, pre v 0.1.3
|
localStorage.setItem("lastName", this.lastName as string);
|
||||||
this.$router.push({ name: "account" });
|
this.$router.push({ name: "account" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<QuickNav selected="Projects"></QuickNav>
|
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="p-6 pb-24">
|
<section id="Content" class="p-6 pb-24">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
@@ -108,7 +107,6 @@ import * as didJwt from "did-jwt";
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
||||||
|
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
|
||||||
import { accountsDB, 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 { accessToken, SimpleSigner } from "@/libs/crypto";
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
||||||
@@ -124,7 +122,7 @@ interface Notification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { LMap, LMarker, LTileLayer, QuickNav },
|
components: { LMap, LMarker, LTileLayer },
|
||||||
})
|
})
|
||||||
export default class NewEditProjectView extends Vue {
|
export default class NewEditProjectView extends Vue {
|
||||||
$notify!: (notification: Notification, timeout?: number) => void;
|
$notify!: (notification: Notification, timeout?: number) => void;
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ 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 } })
|
||||||
export default class NewIdentifierView extends Vue {
|
export default class AccountViewView extends Vue {
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|||||||
@@ -102,7 +102,7 @@
|
|||||||
<h3
|
<h3
|
||||||
class="text-xs italic font-medium text-ellipsis whitespace-nowrap overflow-hidden"
|
class="text-xs italic font-medium text-ellipsis whitespace-nowrap overflow-hidden"
|
||||||
>
|
>
|
||||||
Anonymous/Unnamed
|
Anonymous
|
||||||
</h3>
|
</h3>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
|
|
||||||
<!-- Ideally, this button should only be visible when the active account has more than 7 or 11 contacts in their list (we want to limit the grid count above to 8 or 12 accounts to keep it compact) -->
|
<!-- Ideally, this button should only be visible when the active account has more than 7 or 11 contacts in their list (we want to limit the grid count above to 8 or 12 accounts to keep it compact) -->
|
||||||
<router-link
|
<router-link
|
||||||
v-if="allContacts.length >= 7"
|
v-if="allContacts.length > 7"
|
||||||
:to="{ name: 'contact-gives' }"
|
:to="{ name: 'contact-gives' }"
|
||||||
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
|
class="block text-center text-md font-bold uppercase bg-slate-500 text-white px-2 py-3 rounded-md"
|
||||||
>
|
>
|
||||||
@@ -196,8 +196,8 @@
|
|||||||
|
|
||||||
<GiftedDialog
|
<GiftedDialog
|
||||||
ref="customDialog"
|
ref="customDialog"
|
||||||
|
@dialog-result="handleDialogResult"
|
||||||
message="Received from"
|
message="Received from"
|
||||||
:projectId="this.projectId"
|
|
||||||
>
|
>
|
||||||
</GiftedDialog>
|
</GiftedDialog>
|
||||||
</section>
|
</section>
|
||||||
@@ -212,16 +212,18 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
import GiftedDialog from "@/components/GiftedDialog.vue";
|
import GiftedDialog from "@/components/GiftedDialog.vue";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import { accountsDB, db } from "@/db/index";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
|
||||||
import { accessToken } from "@/libs/crypto";
|
import { accessToken } from "@/libs/crypto";
|
||||||
import {
|
import {
|
||||||
|
createAndSubmitGive,
|
||||||
didInfo,
|
didInfo,
|
||||||
GiverInputInfo,
|
GiverInputInfo,
|
||||||
|
GiverOutputInfo,
|
||||||
GiveServerRecord,
|
GiveServerRecord,
|
||||||
|
ResultWithType,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import EntityIcon from "@/components/EntityIcon.vue";
|
import EntityIcon from "@/components/EntityIcon.vue";
|
||||||
import { Account } from "@/db/tables/accounts";
|
|
||||||
|
|
||||||
interface Notification {
|
interface Notification {
|
||||||
group: string;
|
group: string;
|
||||||
@@ -255,7 +257,7 @@ export default class ProjectViewView extends Vue {
|
|||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
await db.open();
|
await db.open();
|
||||||
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
|
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
|
||||||
this.activeDid = settings?.activeDid || "";
|
this.activeDid = settings?.activeDid || "";
|
||||||
this.apiServer = settings?.apiServer || "";
|
this.apiServer = settings?.apiServer || "";
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await db.contacts.toArray();
|
||||||
@@ -264,17 +266,17 @@ export default class ProjectViewView extends Vue {
|
|||||||
const accounts = accountsDB.accounts;
|
const accounts = accountsDB.accounts;
|
||||||
const accountsArr = await accounts?.toArray();
|
const accountsArr = await accounts?.toArray();
|
||||||
this.allMyDids = accountsArr.map((acc) => acc.did);
|
this.allMyDids = accountsArr.map((acc) => acc.did);
|
||||||
const account = accountsArr.find((acc) => acc.did === this.activeDid);
|
const account = accountsArr?.find((acc) => acc.did === this.activeDid);
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
this.LoadProject(identity);
|
this.LoadProject(identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
.where("did")
|
.where("did")
|
||||||
.equals(activeDid)
|
.equals(activeDid)
|
||||||
.first()) as Account;
|
.first();
|
||||||
const identity = JSON.parse(account?.identity || "null");
|
const identity = JSON.parse(account?.identity || "null");
|
||||||
|
|
||||||
if (!identity) {
|
if (!identity) {
|
||||||
@@ -459,6 +461,11 @@ export default class ProjectViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openDialog(contact: GiverInputInfo) {
|
||||||
|
const dialog: GiftedDialog = this.$refs.customDialog as GiftedDialog;
|
||||||
|
dialog.open(contact);
|
||||||
|
}
|
||||||
|
|
||||||
getOpenStreetMapUrl() {
|
getOpenStreetMapUrl() {
|
||||||
// Google URL is https://maps.google.com/?q=LAT,LONG
|
// Google URL is https://maps.google.com/?q=LAT,LONG
|
||||||
return (
|
return (
|
||||||
@@ -473,8 +480,96 @@ export default class ProjectViewView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
openDialog(contact: GiverInputInfo) {
|
handleDialogResult(result: GiverOutputInfo) {
|
||||||
(this.$refs.customDialog as GiftedDialog).open(contact);
|
if (result.action === "confirm") {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.recordGive(
|
||||||
|
result.giver?.did,
|
||||||
|
result.description,
|
||||||
|
result.hours,
|
||||||
|
).then(() => {
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// action was not "confirm" so do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param giverDid may be null
|
||||||
|
* @param description may be an empty string
|
||||||
|
* @param hours may be 0
|
||||||
|
*/
|
||||||
|
async recordGive(giverDid?: string, description?: string, hours?: number) {
|
||||||
|
if (!this.activeDid) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "You must select an identity before you can record a give.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!description && !hours) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "You must enter a description or some number of hours.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const identity = await this.getIdentity(this.activeDid);
|
||||||
|
const result = await createAndSubmitGive(
|
||||||
|
this.axios,
|
||||||
|
this.apiServer,
|
||||||
|
identity,
|
||||||
|
giverDid,
|
||||||
|
this.activeDid,
|
||||||
|
description,
|
||||||
|
hours,
|
||||||
|
this.projectId,
|
||||||
|
);
|
||||||
|
if (result.type == "success") {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Success",
|
||||||
|
text: "That gift was recorded.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("Error with give creation:", result);
|
||||||
|
if (result.type != "error") {
|
||||||
|
console.log(
|
||||||
|
"... and it has an unexpected result type of",
|
||||||
|
(result as ResultWithType).type,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const message =
|
||||||
|
result?.error?.userMessage ||
|
||||||
|
"There was an error recording the Give.";
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: message,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
Start Here
|
Start Here
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<!-- id used by puppeteer test script -->
|
<div class="mt-8">
|
||||||
<div id="start-question" class="mt-8">
|
|
||||||
<p class="text-center text-xl mb-4 font-light">
|
<p class="text-center text-xl mb-4 font-light">
|
||||||
Do you have an identity to import?
|
Do you have an identity to import?
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
<template>
|
|
||||||
<QuickNav selected="Profile"></QuickNav>
|
|
||||||
<!-- CONTENT -->
|
|
||||||
<section id="Content" class="p-6 pb-24">
|
|
||||||
<!-- Heading -->
|
|
||||||
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
|
|
||||||
Test
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="mb-8">
|
|
||||||
<h2 class="text-xl font-bold mb-4">Notiwind Alert Test Suite</h2>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: 'alert',
|
|
||||||
type: 'toast',
|
|
||||||
text: 'I\'m a toast. Don\'t mind me.',
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="font-bold uppercase bg-slate-400 text-white px-3 py-2 rounded-md mr-2"
|
|
||||||
>
|
|
||||||
Toast (self-dismiss)
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: 'alert',
|
|
||||||
type: 'info',
|
|
||||||
title: 'Information Alert',
|
|
||||||
text: 'Just wanted you to know.',
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
|
||||||
>
|
|
||||||
Info
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: 'alert',
|
|
||||||
type: 'success',
|
|
||||||
title: 'Success Alert',
|
|
||||||
text: 'Congratulations!',
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="font-bold uppercase bg-emerald-600 text-white px-3 py-2 rounded-md mr-2"
|
|
||||||
>
|
|
||||||
Success
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: 'alert',
|
|
||||||
type: 'warning',
|
|
||||||
title: 'Warning Alert',
|
|
||||||
text: 'You might wanna look at this.',
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="font-bold uppercase bg-amber-600 text-white px-3 py-2 rounded-md mr-2"
|
|
||||||
>
|
|
||||||
Warning
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: 'alert',
|
|
||||||
type: 'danger',
|
|
||||||
title: 'Danger Alert',
|
|
||||||
text: 'Something terrible has happened!',
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="font-bold uppercase bg-rose-600 text-white px-3 py-2 rounded-md mr-2"
|
|
||||||
>
|
|
||||||
Danger
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: 'modal',
|
|
||||||
type: 'notification-permission',
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
|
||||||
>
|
|
||||||
Notif ON
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: 'modal',
|
|
||||||
type: 'notification-mute',
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
|
||||||
>
|
|
||||||
Notif MUTE
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
@click="
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: 'modal',
|
|
||||||
type: 'notification-off',
|
|
||||||
},
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
class="font-bold uppercase bg-slate-600 text-white px-3 py-2 rounded-md mr-2"
|
|
||||||
>
|
|
||||||
Notif OFF
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</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>
|
|
||||||
Reference in New Issue
Block a user