Compare commits

..

40 Commits

Author SHA1 Message Date
97274a701d update derivation verbiage 2023-08-21 08:42:18 -06:00
81a6d73f2f update task for alert fixes 2023-08-21 07:31:28 -06:00
5804f692b7 update to new alerting -- old alerts were broken 2023-08-21 07:29:26 -06:00
257aa8d49e Merge branch 'master' into increment-derived 2023-08-21 07:27:33 -04:00
34806b514b log bug with "Share Your Info" button 2023-08-21 06:05:08 -06:00
0024238ca8 Merge pull request 'Added modal dialog for notification permission setting' (#57) from notification-request-permission-dialog into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#57
2023-08-21 03:02:23 -04:00
0af05b4b0d Merge branch 'master' into notification-request-permission-dialog 2023-08-21 03:02:03 -04:00
b9d59eb642 Merge pull request 'allow use of custom derivation path, and add way to increment derivation for existing' (#56) from increment-derived into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#56
2023-08-21 02:59:35 -04:00
0c05505c46 allow use of custom derivation path, and add way to increment derivation for existing 2023-08-20 19:46:12 -06:00
98c093f655 add potential tasks for multiple derivation paths 2023-08-20 06:29:29 -06:00
88112e0629 add note for deployment 2023-08-19 19:10:32 -06:00
Jose Olarte III
6ab92a83bd Added modal dialog for notification permission setting 2023-08-18 21:38:26 +08:00
Jose Olarte III
bfc52151c0 Restored anonymous item in home share section 2023-08-10 18:44:27 +08:00
Jose Olarte III
868b5413de Added jdenticon to project view 2023-08-10 18:41:17 +08:00
Jose Olarte III
50005a0dc3 Removed duplicate item heading 2023-08-10 18:21:15 +08:00
Jose Olarte III
9247b6ed1f Changed ID to name 2023-08-10 18:20:38 +08:00
Jose Olarte III
75f26ccf2d eslint fixes 2023-08-10 18:16:20 +08:00
Matthew Aaron Raymer
bfd2498906 Merge branch 'master' of ssh://173.199.124.46:222/trent_larson/kick-starter-for-time-pwa 2023-08-07 16:33:47 +08:00
Matthew Aaron Raymer
4933017e9c Merge remote-tracking branch 'origin/cleanup-and-qr-code' 2023-08-07 16:33:37 +08:00
Matthew Aaron Raymer
18c23451bb Merge remote-tracking branch 'origin/contact-amounts-ui-improvements' 2023-08-07 16:10:34 +08:00
6d1756b4a5 Merge pull request 'update URL for API server' (#55) from update-api-server into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#55
2023-08-07 03:13:40 -04:00
ac4c92d8e8 Merge branch 'master' into update-api-server 2023-08-07 03:13:26 -04:00
937a3cb6c6 Merge pull request 'Minor cleanup' (#54) from seed-backup-view-improvements into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#54
2023-08-07 03:11:23 -04:00
bf6830a1a8 update URL for API server 2023-08-05 18:20:03 -06:00
Jose Olarte III
5addc3c206 Visual fixes 2023-07-31 21:36:05 +08:00
Jose Olarte III
69f2f3cfd2 Converted to tabular structure
For more adaptive widths
2023-07-31 21:32:19 +08:00
be348461f1 Update 'project.task.yaml' 2023-07-19 22:07:02 -04:00
6e2c596030 proposed move to jdenticon 2023-07-19 22:06:30 -04:00
Matthew Raymer
c502869c5f Add button with gift icon for future dialog 2023-07-19 18:41:12 +08:00
Matthew Raymer
b7aacd63e6 Add and edit project tasks list 2023-07-19 18:26:59 +08:00
Matthew Raymer
5bc0e27b30 Use a DID instead of a name ... this may need some better design on the dialog @jose 2023-07-19 18:25:58 +08:00
Matthew Raymer
a4fe94f081 Add a back arrow 2023-07-19 18:25:03 +08:00
Matthew Raymer
8de95566df Cleaning up this page to switch to GiftedDialog 2023-07-19 18:23:55 +08:00
Matthew Raymer
97569697f6 * show DID if no name
* hide no contacts when there are no contacts
* replace contact property with giver (? can you have another contact give you something ?)
2023-07-19 18:22:35 +08:00
Matthew Raymer
b9ed9d748b Only project owner may see edit button of a project 2023-07-19 18:19:29 +08:00
Matthew Raymer
790d44db81 Remove the stub context menu causing vertical ellipsis 2023-07-19 16:30:56 +08:00
e2bf469dc1 set assignees on several tasks. 2023-07-19 02:26:44 -04:00
592ffacebc possible image uploader 2023-07-19 02:16:28 -04:00
b706e65598 Remove completed tasks. 2023-07-19 02:11:18 -04:00
Matthew Raymer
6e3066ae92 Stub update of project task list 2023-07-18 21:04:48 +08:00
20 changed files with 488 additions and 146 deletions

View File

@@ -11,6 +11,9 @@ npm run serve
``` ```
### Compiles and minifies for production ### Compiles and minifies for production
If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
``` ```
npm run build npm run build
``` ```

View File

@@ -1,45 +1,55 @@
tasks: tasks:
- 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 - .2 bug - on contacts view, click on "to" & "from" and nothing happens
- 01 add a location for a project via map pin : - 01 add a location for a project via map pin :
- add with a "location" field containing this: { "geo":{ "@type":"GeoCoordinates", "latitude":40.883944, "longitude":-111.884787 } } - add with a "location" field containing this: { "geo":{ "@type":"GeoCoordinates", "latitude":40.883944, "longitude":-111.884787 } }
- 04 search by a bounding box for local projects (see API by clicking on "Nearby")
- 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:jose
- 02 Fix images on projectview - allow choice of image from a pallete of images or a url image.
- 08 Scan QR code to import into contacts.
- 40 notifications : - 40 notifications :
- push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data - push, where we trigger a ServiceWorker(?) in the app to reach out and check for new data assignee:matthew
- refactor UI : - 01 add a location for a project via map pin
- .5 Alerts show at the top and can be missed if you've scrolled down on the page, eg. account data download - 04 search by a bounding box for local projects (see API by clicking on "Nearby")
- .2 Make alerts at the top more visible (because they're currently a similar color and sometimes aren't seen) - 01 Replace Gifted/Give in ContactsView with GiftedDialog assignee:matthew
- 02 Fix images on projectview - allow choice of image from a pallete of images or a url image (discovery page display also)
- SEE: https://github.com/dmester/jdenticon assignee:jose
- Show pop-up or some message confirming that settings & contacts download has been initiated/finished - 08 Scan QR code to import into contacts assignee:matthew
- SEE: https://github.com/gruhn/vue-qrcode-reader
- Ensure each action sent to the server has a confirmation - eg registration - 01 Show pop-up or some message confirming that settings & contacts download has been initiated/finished assignee:matthew
- 01 Ensure each action sent to the server has a confirmation - eg registration (ie a toast something that dismisses after 5-10s)
- SEE: https://github.com/emmanuelsw/notiwind assignee:jose
- Home Feed & Quick Give screen : - Home Feed & Quick Give screen :
- 01 save the feed-viewed status in settings storage ("afterQuery") - 01 save the feed-viewed status in settings storage ("afterQuery")
- 01 quick action - send action, maybe choose via canvas tool https://github.com/konvajs/vue-konva - 01 quick action - send action, maybe choose via canvas tool
- SEE: https://github.com/konvajs/vue-konva
- 24 Move to Vite - 24 Move to Vite assignee:matthew
- .5 include the hash of the latest commit, and maybe a version
- .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 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 there are three dots at the top of ProjectViewView that refreshes the page but doesn't do anything else
- 01 fix images on project page, on discovery page
- .2 on ProjectViewView, show different messages for "to" and "from" sections if none exist - .2 on ProjectViewView, show different messages for "to" and "from" sections if none exist
- .2 fix static icon to the right on project page (Matthew - I've made "Rotary" into issuer?) - .2 fix static icon to the right on project page (Matthew - I've made "Rotary" into issuer?) assignee:jose
- .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
- .2 move 'switch identity' to the advanced section - .2 move 'switch identity' to the advanced section
- .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"
- Discuss whether the remaining tasks are worthwhile before MVP release. - Discuss whether the remaining tasks are worthwhile before MVP release.
- 04 allow user to download claims, mine + ones I can see about me from others
- .5 change the derivation path, and regenerate test IDs
- 02 allow user to create new DIDs from the same seed phrase (ie. increment derivation path)
- .5 on ProjectView page, show immediate feedback when a gift is given (on list?) -- and consider the same for Home & Contacts pages
- .5 customize favicon
- .5 Do we want to combine first name & last name?
- .2 Show a warning if both giver and recipient are the same (but still allow?)
- contacts v+ : - contacts v+ :
- 01 Import all the non-sensitive data (ie. contacts & settings). - 01 Import all the non-sensitive data (ie. contacts & settings).
- .2 show error to user when adding a duplicate contact - .2 show error to user when adding a duplicate contact
@@ -52,12 +62,6 @@ tasks:
- maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version") - maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version")
- 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie) - 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
- .5 on ProjectView page, show immediate feedback when a gift is given (on list?) -- and consider the same for Home & Contacts pages
- .5 customize favicon
- 04 allow user to download claims, mine + ones I can see about me from others
- Do we want to combine first name & last name?
- Show a warning if both giver and recipient are the same (but still allow?)
- Release Minimum Viable Product : - Release Minimum Viable Product :
- 08 thorough testing for errors & edge cases - 08 thorough testing for errors & edge cases
- Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot). - Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot).

View File

@@ -1,6 +1,7 @@
<template> <template>
<router-view /> <router-view />
<!-- https://github.com/emmanuelsw/notiwind -->
<NotificationGroup group="alert"> <NotificationGroup group="alert">
<div <div
class="fixed top-4 right-4 w-full max-w-sm flex flex-col items-start justify-end" class="fixed top-4 right-4 w-full max-w-sm flex flex-col items-start justify-end"
@@ -127,6 +128,63 @@
</Notification> </Notification>
</div> </div>
</NotificationGroup> </NotificationGroup>
<NotificationGroup group="modal">
<div class="fixed z-[100] top-0 inset-x-0 w-full">
<Notification
v-slot="{ notifications, close }"
enter="transform ease-out duration-300 transition"
enter-from="translate-y-2 opacity-0 sm:translate-y-4"
enter-to="translate-y-0 opacity-100 sm:translate-y-0"
leave="transition ease-in duration-500"
leave-from="opacity-100"
leave-to="opacity-0"
move="transition duration-500"
move-delay="delay-300"
>
<div
v-for="notification in notifications"
:key="notification.id"
class="w-full"
role="alert"
>
<div
v-if="notification.type === 'notification-permission'"
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">
<p class="text-lg mb-4">
Would you like to turn on notifications for this app?
</p>
<button
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
>
Turn on Notifications
</button>
<div class="grid grid-cols-2 gap-2">
<button
@click="close(notification.id)"
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
>
Maybe Later
</button>
<button
class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md"
>
Never
</button>
</div>
</div>
</div>
</div>
</div>
</Notification>
</div>
</NotificationGroup>
</template> </template>
<style></style> <style></style>

View File

@@ -4,7 +4,7 @@
export enum AppString { export enum AppString {
APP_NAME = "Kick-Start with Time", APP_NAME = "Kick-Start with Time",
PROD_ENDORSER_API_SERVER = "https://endorser.ch:3000", PROD_ENDORSER_API_SERVER = "https://api.endorser.ch",
TEST_ENDORSER_API_SERVER = "https://test.endorser.ch:8000", TEST_ENDORSER_API_SERVER = "https://test.endorser.ch:8000",
LOCAL_ENDORSER_API_SERVER = "http://localhost:3000", LOCAL_ENDORSER_API_SERVER = "http://localhost:3000",

View File

@@ -28,12 +28,12 @@ type NonsensitiveTables = {
* https://9to5answer.com/how-to-bypass-warning-unexpected-any-specify-a-different-type-typescript-eslint-no-explicit-any * https://9to5answer.com/how-to-bypass-warning-unexpected-any-specify-a-different-type-typescript-eslint-no-explicit-any
*/ */
export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T; export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T;
export const accountsDB = new BaseDexie("KickStartAccounts") as SensitiveDexie; export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
const SensitiveSchemas = Object.assign({}, AccountsSchema); const SensitiveSchemas = Object.assign({}, AccountsSchema);
export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> = export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> =
BaseDexie & T; BaseDexie & T;
export const db = new BaseDexie("KickStart") as NonsensitiveDexie; export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie;
const NonsensitiveSchemas = Object.assign({}, ContactsSchema, SettingsSchema); const NonsensitiveSchemas = Object.assign({}, ContactsSchema, SettingsSchema);
/** /**

View File

@@ -7,6 +7,8 @@ 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";
export const DEFAULT_ROOT_DERIVATION_PATH = "m/76798669'/0'/0'/0'";
/** /**
* *
* *
@@ -47,17 +49,17 @@ export const newIdentifier = (
*/ */
export const deriveAddress = ( export const deriveAddress = (
mnemonic: string, mnemonic: string,
derivationPath: string = DEFAULT_ROOT_DERIVATION_PATH,
): [string, string, string, string] => { ): [string, string, string, string] => {
const UPORT_ROOT_DERIVATION_PATH = "m/7696500'/0'/0'/0'";
mnemonic = mnemonic.trim().toLowerCase(); mnemonic = mnemonic.trim().toLowerCase();
const hdnode: HDNode = HDNode.fromMnemonic(mnemonic); const hdnode: HDNode = HDNode.fromMnemonic(mnemonic);
const rootNode: HDNode = hdnode.derivePath(UPORT_ROOT_DERIVATION_PATH); const rootNode: HDNode = hdnode.derivePath(derivationPath);
const privateHex = rootNode.privateKey.substring(2); // original starts with '0x' const privateHex = rootNode.privateKey.substring(2); // original starts with '0x'
const publicHex = rootNode.publicKey.substring(2); // original starts with '0x' const publicHex = rootNode.publicKey.substring(2); // original starts with '0x'
const address = rootNode.address; const address = rootNode.address;
return [address, privateHex, publicHex, UPORT_ROOT_DERIVATION_PATH]; return [address, privateHex, publicHex, derivationPath];
}; };
/** /**

View File

@@ -106,7 +106,7 @@ export function didInfo(
} }
/** /**
* For result, see https://endorser.ch:3000/api-docs/#/claims/post_api_v2_claim * For result, see https://api.endorser.ch/api-docs/#/claims/post_api_v2_claim
* *
* @param identity * @param identity
* @param fromDid may be null * @param fromDid may be null

View File

@@ -11,6 +11,8 @@ import "./assets/styles/tailwind.css";
import { library } from "@fortawesome/fontawesome-svg-core"; import { library } from "@fortawesome/fontawesome-svg-core";
import { import {
faArrowLeft,
faArrowRight,
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,
@@ -54,6 +56,8 @@ import {
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
library.add( library.add(
faArrowLeft,
faArrowRight,
faBurst, faBurst,
faCalendar, faCalendar,
faChevronLeft, faChevronLeft,

View File

@@ -91,6 +91,14 @@ const routes: Array<RouteRecordRaw> = [
/* webpackChunkName: "import-account" */ "../views/ImportAccountView.vue" /* webpackChunkName: "import-account" */ "../views/ImportAccountView.vue"
), ),
}, },
{
path: "/import-derive",
name: "import-derive",
component: () =>
import(
/* webpackChunkName: "import-derive" */ "../views/ImportDerivedAccountView.vue"
),
},
{ {
path: "/new-edit-account", path: "/new-edit-account",
name: "new-edit-account", name: "new-edit-account",
@@ -184,8 +192,7 @@ const router = createRouter({
const errorHandler = (error, to, from) => { const errorHandler = (error, to, from) => {
// Handle the error here // Handle the error here
console.error(error, to, from); console.error("Caught in top level error handler:", error, to, from);
console.log("XXXXX");
// You can also perform additional actions, such as displaying an error message or redirecting the user to a specific page // You can also perform additional actions, such as displaying an error message or redirecting the user to a specific page
}; };

View File

@@ -119,10 +119,24 @@
</router-link> </router-link>
<router-link <router-link
:to="{ name: 'identity-switcher' }" :to="{ name: 'identity-switcher' }"
class="block text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-8" 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
@click="
this.$notify(
{
group: 'modal',
type: 'notification-permission',
},
-1,
)
"
class="block w-full text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-8"
>
Turn on Notifications
</button>
<h3 class="text-sm uppercase font-semibold mb-3">Data</h3> <h3 class="text-sm uppercase font-semibold mb-3">Data</h3>

View File

@@ -1,6 +1,18 @@
<template> <template>
<QuickNav selected="Contacts"></QuickNav> <QuickNav selected="Contacts"></QuickNav>
<section id="Content" class="p-6 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Back -->
<router-link
:to="{ name: 'contacts' }"
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
><fa icon="chevron-left" class="fa-fw"></fa
></router-link>
</h1>
</div>
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Given with {{ contact?.name }} Given with {{ contact?.name }}
</h1> </h1>
@@ -11,66 +23,73 @@
</div> </div>
<!-- Results List --> <!-- Results List -->
<div> <table
<div class="border-b border-slate-300 flex"> class="table-auto w-full border-t border-slate-300 text-sm sm:text-base text-center"
<div class="w-1/4"></div> >
<div class="w-1/4">from them</div> <thead class="bg-slate-100">
<div class="w-1/4"></div> <tr class="border-b border-slate-300">
<div class="w-1/4">to them</div> <th></th>
</div> <th class="px-1 py-2">From Them</th>
<div <th></th>
class="border-b border-slate-300 flex" <th class="px-1 py-2">To Them</th>
v-for="record in giveRecords" </tr>
:key="record.id" </thead>
> <tbody>
<div class="w-1/4"> <tr
{{ new Date(record.issuedAt).toLocaleString() }} v-for="record in giveRecords"
</div> :key="record.id"
<div class="w-1/4"> class="border-b border-slate-300"
<span v-if="record.agentDid == contact.did"> >
<div class="font-bold"> <td class="p-1 text-xs sm:text-sm text-left text-slate-500">
{{ record.amount }} {{ record.unit }} {{ new Date(record.issuedAt).toLocaleString() }}
<span v-if="record.amountConfirmed" class="tooltip"> </td>
<fa icon="circle-check" class="text-green-600 fa-fw ml-1" /> <td class="p-1">
<span class="tooltiptext">Confirmed</span> <span v-if="record.agentDid == contact.did">
</span> <div class="font-bold">
<button v-else class="tooltip" @click="confirm(record)"> {{ record.amount }} {{ record.unit }}
<fa icon="circle" class="text-blue-600 fa-fw ml-1" /> <span v-if="record.amountConfirmed" title="Confirmed">
<span class="tooltiptext">Unconfirmed</span> <fa icon="circle-check" class="text-green-600 fa-fw" />
</button> </span>
</div> <button v-else @click="confirm(record)" title="Unconfirmed">
<br /> <fa icon="circle" class="text-blue-600 fa-fw" />
{{ record.description }} </button>
</span> </div>
</div> <div class="italic text-xs sm:text-sm text-slate-500">
<div class="w-1/8"> {{ record.description }}
<span v-if="record.agentDid == contact.did"> </div>
<fa icon="long-arrow-alt-left" class="text-slate-900 fa-fw ml-1" /> </span>
</span> </td>
<span v-else> <td class="p-1">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span v-if="record.agentDid == contact.did">
<fa icon="long-arrow-alt-right" class="text-slate-900 fa-fw ml-1" /> <fa icon="arrow-left" class="text-slate-400 fa-fw" />
</span> </span>
</div> <span v-else>
<div class="w-1/4"> <fa icon="arrow-right" class="text-slate-400 fa-fw" />
<span v-if="record.agentDid != contact.did"> </span>
<div class="font-bold"> </td>
{{ record.amount }} {{ record.unit }} <td class="p-1">
<span v-if="record.amountConfirmed" class="tooltip"> <span v-if="record.agentDid != contact.did">
<fa icon="circle-check" class="text-green-600 fa-fw ml-1" /> <div class="font-bold">
<span class="tooltiptext">Confirmed</span> {{ record.amount }} {{ record.unit }}
</span> <span v-if="record.amountConfirmed" title="Confirmed">
<button v-else class="tooltip" @click="cannotConfirmMessage()"> <fa icon="circle-check" class="text-green-600 fa-fw" />
<fa icon="circle" class="text-slate-600 fa-fw ml-1" /> </span>
<span class="tooltiptext">Unconfirmed</span> <button
</button> v-else
</div> @click="cannotConfirmMessage()"
<br /> title="Unconfirmed"
{{ record.description }} >
</span> <fa icon="circle" class="text-slate-600 fa-fw" />
</div> </button>
</div> </div>
</div> <div class="italic text-xs sm:text-sm text-slate-500">
{{ record.description }}
</div>
</span>
</td>
</tr>
</tbody>
</table>
<AlertMessage <AlertMessage
:alertTitle="alertTitle" :alertTitle="alertTitle"
:alertMessage="alertMessage" :alertMessage="alertMessage"

View File

@@ -17,10 +17,6 @@
:dotsOptions="{ type: 'square' }" :dotsOptions="{ type: 'square' }"
class="flex justify-center" class="flex justify-center"
/> />
<AlertMessage
:alertTitle="alertTitle"
:alertMessage="alertMessage"
></AlertMessage>
</section> </section>
</template> </template>
@@ -32,18 +28,15 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import * as R from "ramda"; import * as R from "ramda";
import { SimpleSigner } from "@/libs/crypto"; import { SimpleSigner } from "@/libs/crypto";
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import AlertMessage from "@/components/AlertMessage";
import QuickNav from "@/components/QuickNav"; import QuickNav from "@/components/QuickNav";
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;
alertTitle = "";
alertMessage = "";
@Component({ @Component({
components: { components: {
QRCodeVue3, QRCodeVue3,
AlertMessage,
QuickNav, QuickNav,
}, },
}) })
@@ -55,7 +48,10 @@ export default class ContactQRScanShow extends Vue {
public async getIdentity(activeDid) { public async getIdentity(activeDid) {
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); const account: Account | undefined = 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) {
@@ -66,15 +62,6 @@ export default class ContactQRScanShow extends Vue {
return identity; return identity;
} }
public async getHeaders(identity) {
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
return headers;
}
async created() { async created() {
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);

View File

@@ -91,7 +91,6 @@
</div> </div>
<div class="grow"> <div class="grow">
<h2 class="text-base font-semibold">{{ project.name }}</h2>
<h2 class="text-base font-semibold">{{ project.name }}</h2> <h2 class="text-base font-semibold">{{ project.name }}</h2>
<div class="text-sm"> <div class="text-sm">
<fa icon="user" class="fa-fw text-slate-400"></fa> <fa icon="user" class="fa-fw text-slate-400"></fa>

View File

@@ -92,6 +92,21 @@
> >
Danger Danger
</button> </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"
>
Notification Permission
</button>
</div> </div>
<div class="mb-8"> <div class="mb-8">
@@ -99,6 +114,18 @@
<p class="mb-4">Show appreciation to a contact:</p> <p class="mb-4">Show appreciation to a contact:</p>
<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()">
<EntityIcon
:entityId="Anonymous"
:iconSize="64"
class="mx-auto border border-slate-300 rounded-md mb-1"
></EntityIcon>
<h3
class="text-xs italic font-medium text-ellipsis whitespace-nowrap overflow-hidden"
>
Anonymous
</h3>
</li>
<li <li
v-for="contact in allContacts" v-for="contact in allContacts"
:key="contact.did" :key="contact.did"
@@ -112,7 +139,7 @@
<h3 <h3
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden" class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
> >
{{ contact.name || "(no name)" }} {{ contact.name || contact.did }}
</h3> </h3>
</li> </li>
</ul> </ul>
@@ -129,6 +156,7 @@
<!-- If there are no contacts, show this instead: --> <!-- If there are no contacts, show this instead: -->
<div <div
class="rounded border border-dashed border-slate-300 bg-slate-100 px-4 py-3 text-center italic text-slate-500" class="rounded border border-dashed border-slate-300 bg-slate-100 px-4 py-3 text-center italic text-slate-500"
v-if="allContacts.length === 0"
> >
(No contacts to show.) (No contacts to show.)
</div> </div>
@@ -350,8 +378,7 @@ export default class HomeView extends Vue {
claim = claim.claim; claim = claim.claim;
} }
// agent.did is for legacy data, before March 2023 // agent.did is for legacy data, before March 2023
const giverDid = const giverDid = claim.agent?.identifier || claim.agent?.did;
claim.agent?.identifier || claim.agent?.did;
const giverInfo = didInfo( const giverInfo = didInfo(
giverDid, giverDid,
this.activeDid, this.activeDid,
@@ -390,7 +417,7 @@ export default class HomeView extends Vue {
handleDialogResult(result) { handleDialogResult(result) {
if (result.action === "confirm") { if (result.action === "confirm") {
return new Promise((resolve) => { return new Promise((resolve) => {
this.recordGive(result.contact?.did, result.description, result.hours); this.recordGive(result.giver?.did, result.description, result.hours);
resolve(); resolve();
}); });
} else { } else {

View File

@@ -24,6 +24,24 @@
v-model="mnemonic" v-model="mnemonic"
/> />
{{ mnemonic }} {{ mnemonic }}
<h3
class="text-sm uppercase font-semibold mb-3"
@click="showAdvanced = !showAdvanced"
>
Advanced
</h3>
<div v-if="showAdvanced">
Enter a custom derivation path
<input
type="text"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
v-model="derivationPath"
/>
For previous uPort or Endorser users,
<a @click="derivationPath = UPORT_DERIVATION_PATH" class="text-blue-500">
click here to use that value.
</a>
</div>
<div class="mt-8"> <div class="mt-8">
<button <button
@click="from_mnemonic()" @click="from_mnemonic()"
@@ -44,7 +62,11 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { deriveAddress, newIdentifier } from "../libs/crypto"; import {
DEFAULT_ROOT_DERIVATION_PATH,
deriveAddress,
newIdentifier,
} from "../libs/crypto";
import { accountsDB, db } from "@/db"; import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@@ -52,11 +74,14 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
components: {}, components: {},
}) })
export default class ImportAccountView extends Vue { export default class ImportAccountView extends Vue {
UPORT_DERIVATION_PATH = "m/7696500'/0'/0'/0'";
mnemonic = ""; mnemonic = "";
address = ""; address = "";
privateHex = ""; privateHex = "";
publicHex = ""; publicHex = "";
derivationPath = ""; derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
showAdvanced = false;
public onCancelClick() { public onCancelClick() {
this.$router.back(); this.$router.back();
@@ -65,8 +90,10 @@ export default class ImportAccountView extends Vue {
public async from_mnemonic() { public async from_mnemonic() {
const mne: string = this.mnemonic.trim().toLowerCase(); const mne: string = this.mnemonic.trim().toLowerCase();
if (this.mnemonic.trim().length > 0) { if (this.mnemonic.trim().length > 0) {
[this.address, this.privateHex, this.publicHex, this.derivationPath] = [this.address, this.privateHex, this.publicHex] = deriveAddress(
deriveAddress(mne); mne,
this.derivationPath,
);
const newId = newIdentifier( const newId = newIdentifier(
this.address, this.address,

View File

@@ -0,0 +1,163 @@
<template>
<section id="Content" class="p-6 pb-24">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Cancel -->
<button
@click="$router.go(-1)"
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
>
<fa icon="chevron-left"></fa>
</button>
Derive from Existing Identity
</h1>
</div>
<!-- Import Account Form -->
<div>
<p class="text-center text-xl mb-4 font-light">
Will increment the maximum derivation path from the existing seed.
</p>
<p v-if="didArrays.length > 1">
Choose existing DIDs from same seed phrase to compute derivation.
</p>
<ul class="mb-4">
<li
class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-2"
v-for="dids in didArrays"
:key="dids[0]"
@click="switchAccount(dids[0])"
>
<fa
v-if="dids[0] == selectedArrayFirstDid"
icon="circle"
class="fa-fw text-blue-400 text-xl mr-3"
></fa>
<fa
v-else
icon="circle"
class="fa-fw text-slate-400 text-xl mr-3"
></fa>
<span class="overflow-hidden">
<div class="text-sm text-slate-500 truncate">
<code>{{ dids.join(",") }}</code>
</div>
</span>
</li>
</ul>
</div>
<div class="mt-8">
<button
@click="incrementDerivation()"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
>
Increment and Import
</button>
<button
@click="onCancelClick()"
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import {
DEFAULT_ROOT_DERIVATION_PATH,
deriveAddress,
newIdentifier,
} from "../libs/crypto";
import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component({
components: {},
})
export default class ImportAccountView extends Vue {
derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
didArrays: Array<Array<string>> = [];
selectedArrayFirstDid = "";
async mounted() {
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const seedDids = {};
accounts.forEach((account) => {
const prevDids = seedDids[account.mnemonic] || [];
seedDids[account.mnemonic] = prevDids.concat([account.did]);
});
this.didArrays = Object.values(seedDids);
this.selectedArrayFirstDid = this.didArrays[0][0];
}
public onCancelClick() {
this.$router.back();
}
public switchAccount(did: string) {
this.selectedArrayFirstDid = did;
}
public async incrementDerivation() {
await accountsDB.open();
// find the maximum derivation path for the selected DIDs
const selectedArray: Array<string> = this.didArrays.find(
(dids) => dids[0] === this.selectedArrayFirstDid,
);
const allMatchingAccounts = await accountsDB.accounts
.where("did")
.anyOf(...selectedArray)
.toArray();
const accountWithMaxDeriv = allMatchingAccounts[0];
allMatchingAccounts.slice(1).forEach((account) => {
if (account.derivationPath > accountWithMaxDeriv.derivationPath) {
accountWithMaxDeriv.derivationPath = account.derivationPath;
}
});
// increment the last number in that max derivation path
let lastStr = accountWithMaxDeriv.derivationPath.split("/").slice(-1)[0];
if (lastStr.endsWith("'")) {
lastStr = lastStr.slice(0, -1);
}
const lastNum = parseInt(lastStr, 10);
const newLastNum = lastNum + 1;
const newDerivPath = accountWithMaxDeriv.derivationPath
.split("/")
.slice(0, -1)
.concat([newLastNum.toString() + "'"])
.join("/");
const mne: string = accountWithMaxDeriv.mnemonic;
const [address, privateHex, publicHex] = deriveAddress(mne, newDerivPath);
const newId = newIdentifier(address, publicHex, privateHex, newDerivPath);
try {
await accountsDB.accounts.add({
dateCreated: new Date().toISOString(),
derivationPath: newDerivPath,
did: newId.did,
identity: JSON.stringify(newId),
mnemonic: mne,
publicKeyHex: newId.keys[0].publicKeyHex,
});
// record that as the active DID
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: newId.did,
});
this.$router.push({ name: "account" });
} catch (err) {
console.error("Error saving mnemonic & updating settings:", err);
}
}
}
</script>

View File

@@ -218,13 +218,13 @@ export default class NewEditProjectView extends Vue {
try { try {
const resp = await this.axios.post(url, payload, { headers }); const resp = await this.axios.post(url, payload, { headers });
// handleId is new in server v release-1.6.0; remove fullIri when that // handleId is new in server v release-1.6.0; remove fullIri when that
// version shows up here: https://endorser.ch:3000/api-docs/ // version shows up here: https://api.endorser.ch/api-docs/
if (resp.data?.success?.handleId || resp.data?.success?.fullIri) { if (resp.data?.success?.handleId || resp.data?.success?.fullIri) {
this.errorMessage = ""; this.errorMessage = "";
this.alertTitle = ""; this.alertTitle = "";
this.alertMessage = ""; this.alertMessage = "";
// handleId is new in server v release-1.6.0; remove fullIri when that // handleId is new in server v release-1.6.0; remove fullIri when that
// version shows up here: https://endorser.ch:3000/api-docs/ // version shows up here: https://api.endorser.ch/api-docs/
useAppStore().setProjectId( useAppStore().setProjectId(
resp.data.success.handleId || resp.data.success.fullIri, resp.data.success.handleId || resp.data.success.fullIri,
); );

View File

@@ -12,13 +12,6 @@
> >
<fa icon="chevron-left" class="fa-fw"></fa> <fa icon="chevron-left" class="fa-fw"></fa>
</button> </button>
<!-- Context Menu -->
<a
href=""
class="text-lg text-center px-2 py-1 absolute -right-2 -top-1"
><fa icon="ellipsis-vertical" class="fa-fw"></fa
></a>
View Plan View Plan
</h1> </h1>
</div> </div>
@@ -38,13 +31,13 @@
<div class="overflow-hidden"> <div class="overflow-hidden">
<h2 class="text-xl font-semibold">{{ name }}</h2> <h2 class="text-xl font-semibold">{{ name }}</h2>
<div class="text-sm mb-3"> <div class="text-sm mb-3">
<div class="truncate" <div class="truncate">
><fa icon="user" class="fa-fw text-slate-400"></fa> <fa icon="user" class="fa-fw text-slate-400"></fa>
{{ issuer }}</div {{ issuer }}
> </div>
<div <div>
><fa icon="calendar" class="fa-fw text-slate-400"></fa <fa icon="calendar" class="fa-fw text-slate-400"></fa>
>{{ timeSince }} {{ timeSince }}
</div> </div>
</div> </div>
</div> </div>
@@ -68,6 +61,7 @@
</div> </div>
</div> </div>
<button <button
v-if="issuer == activeDid"
type="button" type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@click="onEditClick()" @click="onEditClick()"
@@ -90,9 +84,11 @@
<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()">
<div class="mb-1"> <EntityIcon
<fa icon="question-circle" class="fa-fw fa-xl text-slate-400"></fa> :entityId="Anonymous"
</div> :iconSize="64"
class="mx-auto border border-slate-300 rounded-md mb-1"
></EntityIcon>
<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"
> >
@@ -104,9 +100,11 @@
:key="contact.did" :key="contact.did"
@click="openDialog(contact)" @click="openDialog(contact)"
> >
<div class="mb-1"> <EntityIcon
<fa icon="user" class="fa-fw fa-xl text-slate-400"></fa> :entityId="contact.did"
</div> :iconSize="64"
class="mx-auto border border-slate-300 rounded-md mb-1"
></EntityIcon>
<h3 <h3
class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden" class="text-xs font-medium text-ellipsis whitespace-nowrap overflow-hidden"
> >

View File

@@ -28,6 +28,14 @@
<i>Don't take a screenshot or send it to any online service.</i> <i>Don't take a screenshot or send it to any online service.</i>
</p> </p>
<p v-if="numAccounts > 1">
<b class="text-orange-600">Note:</b> You have more than one identity
stored in this browser. If they are all based on the same seed as the
current identity, this one backup is sufficient; however, if you have
different seeds for other identities, you will have to back them up
separately.
</p>
<div class="bg-slate-100 rounded-md overflow-hidden p-4 mb-4"> <div class="bg-slate-100 rounded-md overflow-hidden p-4 mb-4">
<button <button
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@@ -60,6 +68,7 @@ import QuickNav from "@/components/QuickNav";
@Component({ components: { AlertMessage, QuickNav } }) @Component({ components: { AlertMessage, QuickNav } })
export default class SeedBackupView extends Vue { export default class SeedBackupView extends Vue {
activeAccount = null; activeAccount = null;
numAccounts = 0;
showSeed = false; showSeed = false;
alertMessage = ""; alertMessage = "";
alertTitle = ""; alertTitle = "";
@@ -73,6 +82,7 @@ export default class SeedBackupView extends Vue {
await accountsDB.open(); await accountsDB.open();
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();
this.numAccounts = accounts.length;
this.activeAccount = R.find((acc) => acc.did === activeDid, accounts); this.activeAccount = R.find((acc) => acc.did === activeDid, accounts);
} catch (err) { } catch (err) {
console.error("Got an error loading an identity:", err); console.error("Got an error loading an identity:", err);

View File

@@ -10,30 +10,46 @@
<div class="mt-8"> <div 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 already have an identity to import? Do you have an identity to import?
</p> </p>
<a <a
@click="onClickYes()" @click="onClickYes()"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2" class="block w-full text-center text-lg uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
> >
No No
</a> </a>
<a <a
@click="onClickNo()" @click="onClickNo()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-2"
>Yes</a
> >
Yes
</a>
<a
v-if="numAccounts > 0"
@click="onClickDerive()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-2"
>
Derive New Address from Seed Imported Previously
</a>
</div> </div>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB } from "@/db";
@Component({ @Component({
components: {}, components: {},
}) })
export default class StartView extends Vue { export default class StartView extends Vue {
numAccounts = 0;
async mounted() {
await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();
}
public onClickYes() { public onClickYes() {
this.$router.push({ name: "new-identifier" }); this.$router.push({ name: "new-identifier" });
} }
@@ -41,5 +57,9 @@ export default class StartView extends Vue {
public onClickNo() { public onClickNo() {
this.$router.push({ name: "import-account" }); this.$router.push({ name: "import-account" });
} }
public onClickDerive() {
this.$router.push({ name: "import-derive" });
}
} }
</script> </script>