Compare commits

...

2 Commits

  1. 7
      CHANGELOG.md
  2. 11
      README.md
  3. 13
      src/App.vue
  4. 11
      src/components/FeedFilters.vue
  5. 10
      src/components/GiftedDialog.vue
  6. 10
      src/components/OfferDialog.vue
  7. 8
      src/components/PhotoDialog.vue
  8. 11
      src/components/TopMessage.vue
  9. 9
      src/components/UserNameDialog.vue
  10. 10
      src/components/World/components/objects/landmarks.js
  11. 84
      src/db/index.ts
  12. 16
      src/db/tables/settings.ts
  13. 6
      src/libs/endorserServer.ts
  14. 40
      src/libs/util.ts
  15. 14
      src/test/index.ts
  16. 77
      src/views/AccountViewView.vue
  17. 10
      src/views/ClaimAddRawView.vue
  18. 12
      src/views/ClaimView.vue
  19. 20
      src/views/ConfirmGiftView.vue
  20. 10
      src/views/ContactAmountsView.vue
  21. 10
      src/views/ContactGiftingView.vue
  22. 7
      src/views/ContactImportView.vue
  23. 40
      src/views/ContactQRScanShowView.vue
  24. 26
      src/views/ContactsView.vue
  25. 128
      src/views/DIDView.vue
  26. 13
      src/views/DiscoverView.vue
  27. 10
      src/views/GiftedDetailsView.vue
  28. 50
      src/views/HomeView.vue
  29. 13
      src/views/IdentitySwitcherView.vue
  30. 11
      src/views/NewEditAccountView.vue
  31. 10
      src/views/NewEditProjectView.vue
  32. 10
      src/views/OfferDetailsView.vue
  33. 12
      src/views/ProjectViewView.vue
  34. 66
      src/views/ProjectsView.vue
  35. 10
      src/views/QuickActionBvcBeginView.vue
  36. 10
      src/views/QuickActionBvcEndView.vue
  37. 7
      src/views/SearchAreaView.vue
  38. 8
      src/views/SeedBackupView.vue
  39. 14
      src/views/ShareMyContactInfoView.vue
  40. 7
      src/views/SharedPhotoView.vue
  41. 8
      src/views/StartView.vue
  42. 10
      src/views/TestView.vue
  43. 31
      test-playwright/00-noid-tests.spec.ts
  44. 32
      test-playwright/40-add-contact.spec.ts
  45. 22
      test-playwright/testUtils.ts

7
CHANGELOG.md

@ -6,6 +6,13 @@ 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).
## [0.3.?]
### Added
- Separate 'isRegistered' flag for each account
### Fixed
- Alert when looking at one's own activity if not in contacts.
## [0.3.25] - 2024.08.30 - dcbe02d877aecb4cdef2643d90e6595d246a9f82
### Added
- "Ideas" now jumps directly to giving prompt or contact list.

11
README.md

@ -97,7 +97,7 @@ It's possible to use the global test Endorser (ledger) server (but currently the
It's possible to run with a minimal set of data: the following starts with the bare minimum of test data (but currently the tests don't all succeed):
It's possible to run with a minimal set of data; the following starts with the bare minimum of test data:
```
rm ../endorser-ch-test-local.sqlite3
NODE_ENV=test-local npm run flyway migrate
@ -106,6 +106,15 @@ NODE_ENV=test-local npm run dev
```
To run a single test with the screenshots, use the following:
```
npx playwright test test-playwright/40-add-contact.spec.ts --trace on
```
... with the `-c playwright.config-local.ts` to get the same results as above.
### Register new user on test server
On the test server, User #0 has rights to register others, so you can start

13
src/App.vue

@ -375,7 +375,10 @@
<script lang="ts">
import axios from "axios";
import { Vue, Component } from "vue-facing-decorator";
import { Router } from "vue-router";
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import { retrieveSettingsForActiveAccount } from "@/db/index";
import * as libsUtil from "@/libs/util";
interface ServiceWorkerMessage {
@ -405,11 +408,6 @@ interface PushSubscriptionWithTime extends PushSubscriptionJSON {
notifyTime: { utcHour: number };
}
import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { sendTestThroughPushServer } from "@/libs/util";
@Component
export default class App extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
@ -422,8 +420,7 @@ export default class App extends Vue {
async mounted() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const settings = await retrieveSettingsForActiveAccount();
let pushUrl = DEFAULT_PUSH_SERVER;
if (settings?.webPushServer) {
pushUrl = settings.webPushServer;
@ -640,7 +637,7 @@ export default class App extends Vue {
console.log(
"Subscription data sent to server and all finished successfully.",
);
await sendTestThroughPushServer(subscription, true);
await libsUtil.sendTestThroughPushServer(subscription, true);
this.$notify(
{
group: "alert",

11
src/components/FeedFilters.vue

@ -100,7 +100,7 @@ import {
} from "@vue-leaflet/vue-leaflet";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { db } from "@/db/index";
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
@Component({
components: {
@ -121,11 +121,10 @@ export default class FeedFilters extends Vue {
async open(onCloseIfChanged: () => void) {
this.onCloseIfChanged = onCloseIfChanged;
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.hasVisibleDid = !!settings?.filterFeedByVisible;
this.isNearby = !!settings?.filterFeedByNearby;
if (settings?.searchBoxes && settings.searchBoxes.length > 0) {
const settings = await retrieveSettingsForActiveAccount();
this.hasVisibleDid = !!settings.filterFeedByVisible;
this.isNearby = !!settings.filterFeedByNearby;
if (settings.searchBoxes && settings.searchBoxes.length > 0) {
this.hasSearchBox = true;
}

10
src/components/GiftedDialog.vue

@ -91,8 +91,7 @@ import { Vue, Component, Prop } from "vue-facing-decorator";
import { NotificationIface } from "@/constants/app";
import { createAndSubmitGive, didInfo } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
@Component
@ -138,10 +137,9 @@ export default class GiftedDialog extends Vue {
this.offerId = offerId || "";
try {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
this.allContacts = await db.contacts.toArray();

10
src/components/OfferDialog.vue

@ -85,8 +85,7 @@ import { Vue, Component, Prop } from "vue-facing-decorator";
import { NotificationIface } from "@/constants/app";
import { createAndSubmitOffer } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { retrieveSettingsForActiveAccount } from "@/db/index";
@Component
export default class OfferDialog extends Vue {
@ -113,10 +112,9 @@ export default class OfferDialog extends Vue {
this.recipientDid = recipientDid;
this.recipientName = recipientName;
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {

8
src/components/PhotoDialog.vue

@ -126,8 +126,7 @@ import { Component, Vue } from "vue-facing-decorator";
import VuePictureCropper, { cropper } from "vue-picture-cropper";
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { retrieveSettingsForActiveAccount } from "@/db/index";
import { accessToken } from "@/libs/crypto";
@Component({ components: { Camera, VuePictureCropper } })
@ -151,9 +150,8 @@ export default class PhotoDialog extends Vue {
async mounted() {
try {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.activeDid = settings?.activeDid || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
console.error("Error retrieving settings from database:", err);

11
src/components/TopMessage.vue

@ -16,8 +16,7 @@
import { Component, Vue, Prop } from "vue-facing-decorator";
import { AppString, NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { retrieveSettingsForActiveAccount } from "@/db/index";
@Component
export default class TopMessage extends Vue {
@ -29,17 +28,15 @@ export default class TopMessage extends Vue {
async mounted() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const settings = await retrieveSettingsForActiveAccount();
if (
settings?.warnIfTestServer &&
settings.warnIfTestServer &&
settings.apiServer !== AppString.PROD_ENDORSER_API_SERVER
) {
const didPrefix = settings.activeDid?.slice(11, 15);
this.message = "You're linked to a non-prod server, user " + didPrefix;
} else if (
settings?.warnIfProdServer &&
settings.warnIfProdServer &&
settings.apiServer === AppString.PROD_ENDORSER_API_SERVER
) {
const didPrefix = settings.activeDid?.slice(11, 15);

9
src/components/UserNameDialog.vue

@ -39,8 +39,8 @@
import { Vue, Component } from "vue-facing-decorator";
import { NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component
export default class UserNameDialog extends Vue {
@ -52,9 +52,8 @@ export default class UserNameDialog extends Vue {
async open(aCallback?: (name?: string) => void) {
this.callback = aCallback || this.callback;
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.givenName = settings?.firstName || "";
const settings = await retrieveSettingsForActiveAccount();
this.givenName = settings.firstName || "";
this.visible = true;
}

10
src/components/World/components/objects/landmarks.js

@ -3,8 +3,7 @@ import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader";
import * as SkeletonUtils from "three/addons/utils/SkeletonUtils";
import * as TWEEN from "@tweenjs/tween.js";
import { db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { retrieveSettingsForActiveAccount } from "@/db";
import { getHeaders } from "@/libs/endorserServer";
const ANIMATION_DURATION_SECS = 10;
@ -14,10 +13,9 @@ export async function loadLandmarks(vue, world, scene, loop) {
vue.setWorldProperty("animationDurationSeconds", ANIMATION_DURATION_SECS);
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const activeDid = settings?.activeDid || "";
const apiServer = settings?.apiServer;
const settings = await retrieveSettingsForActiveAccount();
const activeDid = settings.activeDid || "";
const apiServer = settings.apiServer;
const headers = await getHeaders(activeDid);
const url = apiServer + "/api/v2/report/claims?claimType=GiveAction";

84
src/db/index.ts

@ -1,5 +1,7 @@
import BaseDexie, { Table } from "dexie";
import { encrypted, Encryption } from "@pvermeer/dexie-encrypted-addon";
import * as R from "ramda";
import { Account, AccountsSchema } from "./tables/accounts";
import { Contact, ContactSchema } from "./tables/contacts";
import { Log, LogSchema } from "./tables/logs";
@ -45,15 +47,87 @@ accountsDB.version(1).stores(AccountsSchema);
db.version(2).stores({
...ContactSchema,
...LogSchema,
...SettingsSchema,
...{ settings: "id" }, // old Settings schema
});
// v3 added Temp
db.version(3).stores(TempSchema);
db.version(4)
.stores(SettingsSchema)
.upgrade((tx) => {
return tx
.table("settings")
.toCollection()
.modify((settings) => {
settings.accountDid = ""; // make it non-null for the default master settings, but still indexable
});
});
const DEFAULT_SETTINGS = {
id: MASTER_SETTINGS_KEY,
activeDid: undefined,
apiServer: DEFAULT_ENDORSER_API_SERVER,
};
// Event handler to initialize the non-sensitive database with default settings
db.on("populate", async () => {
await db.settings.add({
id: MASTER_SETTINGS_KEY,
apiServer: DEFAULT_ENDORSER_API_SERVER,
});
await db.settings.add(DEFAULT_SETTINGS);
});
// retrieves default settings
// calls db.open()
export async function retrieveSettingsForDefaultAccount(): Promise<Settings> {
await db.open();
return (await db.settings.get(MASTER_SETTINGS_KEY)) || DEFAULT_SETTINGS;
}
export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
const defaultSettings = await retrieveSettingsForDefaultAccount();
if (!defaultSettings.activeDid) {
return defaultSettings;
} else {
const overrideSettings =
(await db.settings
.where("accountDid")
.equals(defaultSettings.activeDid)
.first()) || {};
return R.mergeDeepRight(defaultSettings, overrideSettings);
}
}
// Update settings for the given account, or in MASTER_SETTINGS_KEY if no accountDid is provided.
// Don't expose this because we should be explicit on whether we're updating the default settings or account settings.
async function updateSettings(settingsChanges: Settings): Promise<void> {
await db.open();
if (!settingsChanges.accountDid) {
// ensure there is no "id" that would override the key
delete settingsChanges.id;
await db.settings.update(MASTER_SETTINGS_KEY, settingsChanges);
} else {
const result = await db.settings
.where("accountDid")
.equals(settingsChanges.accountDid)
.modify(settingsChanges);
if (result === 0) {
if (!settingsChanges.id) {
// It is unfortunate that we have to set this explicitly.
// We didn't make id a "++id" at the beginning and Dexie won't let us change it,
// plus we made our first settings objects MASTER_SETTINGS_KEY = 1 instead of 0
settingsChanges.id = (await db.settings.count()) + 1;
}
await db.settings.add(settingsChanges);
}
}
}
export async function updateDefaultSettings(settings: Settings): Promise<void> {
delete settings.accountDid; // just in case
await updateSettings(settings);
}
export async function updateAccountSettings(
accountDid: string,
settings: Settings,
): Promise<void> {
settings.accountDid = accountDid;
await updateSettings(settings);
}

16
src/db/tables/settings.ts

@ -12,22 +12,28 @@ export type BoundingBox = {
* Settings type encompasses user-specific configuration details.
*/
export type Settings = {
id: number; // Only one entry, keyed with MASTER_SETTINGS_KEY
// default entry is keyed with MASTER_SETTINGS_KEY; other entries are linked to an account with account ID
id?: number; // this is only blank on input, when the database assigns it
// if supplied, this settings record overrides the master record when the user switches to this account
accountDid?: string; // not used in the MASTER_SETTINGS_KEY entry
// active Decentralized ID
activeDid?: string; // only used in the MASTER_SETTINGS_KEY entry
activeDid?: string; // Active Decentralized ID
apiServer?: string; // API server URL
filterFeedByNearby?: boolean; // filter by nearby
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden
firstName?: string; // user's full name
firstName?: string; // user's full name, may be null if unwanted for a particular account
hideRegisterPromptOnNewContact?: boolean;
isRegistered?: boolean;
imageServer?: string;
lastName?: string; // deprecated - put all names in firstName
lastNotifiedClaimId?: string;
lastViewedClaimId?: string;
passkeyExpirationMinutes?: number; // passkey access token time-to-live in minutes
profileImageUrl?: string;
profileImageUrl?: string; // may be null if unwanted for a particular account
reminderTime?: number; // Time in milliseconds since UNIX epoch for reminders
reminderOn?: boolean; // Toggle to enable or disable reminders
@ -54,7 +60,7 @@ export function isAnyFeedFilterOn(settings: Settings): boolean {
* Schema for the Settings table in the database.
*/
export const SettingsSchema = {
settings: "id",
settings: "id, &accountDid",
};
/**

6
src/libs/endorserServer.ts

@ -84,7 +84,9 @@ export interface GiveSummaryRecord {
amountConfirmed: number;
description: string;
fullClaim: GiveVerifiableCredential;
fulfillsPlanHandleId: string;
fulfillsHandleId: string;
fulfillsPlanHandleId?: string;
fulfillsType?: string;
handleId: string;
issuedAt: string;
issuerDid: string;
@ -519,7 +521,7 @@ const planCache: LRUCache<string, PlanSummaryRecord> = new LRUCache({
* @param apiServer
*/
export async function getPlanFromCache(
handleId: string | null,
handleId: string,
axios: Axios,
apiServer: string,
requesterDid?: string,

40
src/libs/util.ts

@ -6,21 +6,23 @@ import * as R from "ramda";
import { useClipboard } from "@vueuse/core";
import { DEFAULT_PUSH_SERVER } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import {
accountsDB,
retrieveSettingsForActiveAccount,
updateAccountSettings,
updateDefaultSettings,
} from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import {
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
MASTER_SETTINGS_KEY,
} from "@/db/tables/settings";
import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "@/db/tables/settings";
import { deriveAddress, generateSeed, newIdentifier } from "@/libs/crypto";
import * as serverUtil from "@/libs/endorserServer";
import {
containsHiddenDid,
GenericCredWrapper,
GenericVerifiableCredential,
OfferVerifiableCredential,
} from "@/libs/endorserServer";
import * as serverUtil from "@/libs/endorserServer";
import { KeyMeta } from "@/libs/crypto/vc";
import { createPeerDid } from "@/libs/crypto/vc/didPeer";
import { registerCredential } from "@/libs/crypto/vc/passkeyDidPeer";
@ -311,9 +313,9 @@ export const generateSaveAndActivateIdentity = async (): Promise<string> => {
publicKeyHex: newId.keys[0].publicKeyHex,
});
await db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: newId.did,
});
await updateDefaultSettings({ activeDid: newId.did });
console.log("Updated default settings in util");
await updateAccountSettings(newId.did, { isRegistered: false });
return newId.did;
};
@ -341,30 +343,24 @@ export const registerSaveAndActivatePasskey = async (
keyName: string,
): Promise<Account> => {
const account = await registerAndSavePasskey(keyName);
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: account.did,
});
await updateDefaultSettings({ activeDid: account.did });
await updateAccountSettings(account.did, { isRegistered: false });
return account;
};
export const getPasskeyExpirationSeconds = async (): Promise<number> => {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const passkeyExpirationSeconds =
const settings = await retrieveSettingsForActiveAccount();
return (
(settings?.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES) *
60;
return passkeyExpirationSeconds;
60
);
};
export const sendTestThroughPushServer = async (
subscriptionJSON: PushSubscriptionJSON,
skipFilter: boolean,
): Promise<AxiosResponse> => {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const settings = await retrieveSettingsForActiveAccount();
let pushUrl: string = DEFAULT_PUSH_SERVER as string;
if (settings?.webPushServer) {
pushUrl = settings.webPushServer;

14
src/test/index.ts

@ -1,10 +1,9 @@
import axios from "axios";
import * as didJwt from "did-jwt";
import { AppString } from "@/constants/app";
import { db } from "../db";
import { SERVICE_ID } from "../libs/endorserServer";
import { deriveAddress, newIdentifier } from "../libs/crypto";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { retrieveSettingsForActiveAccount } from "../db";
import { SERVICE_ID } from "@/libs/endorserServer";
import { deriveAddress, newIdentifier } from "@/libs/crypto";
/**
* Get User #0 to sign & submit a RegisterAction for the user's activeDid.
@ -17,8 +16,7 @@ export async function testServerRegisterUser() {
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const settings = await retrieveSettingsForActiveAccount();
// Make a claim
const vcClaim = {
@ -26,7 +24,7 @@ export async function testServerRegisterUser() {
"@type": "RegisterAction",
agent: { did: identity0.did },
object: SERVICE_ID,
participant: { did: settings?.activeDid },
participant: { did: settings.activeDid },
};
// Make a payload for the claim
const vcPayload = {
@ -53,7 +51,7 @@ export async function testServerRegisterUser() {
const payload = JSON.stringify({ jwtEncoded: vcJwt });
const endorserApiServer =
settings?.apiServer || AppString.TEST_ENDORSER_API_SERVER;
settings.apiServer || AppString.TEST_ENDORSER_API_SERVER;
const url = endorserApiServer + "/api/claim";
const headers = {
"Content-Type": "application/json",

77
src/views/AccountViewView.vue

@ -9,24 +9,11 @@
Your Identity
</h1>
<div class="flex justify-between mb-2 mt-4">
<span />
<span class="whitespace-nowrap">
<router-link
:to="{ name: 'contact-qr' }"
class="text-xs bg-slate-500 text-white px-1.5 py-1 rounded-md"
>
<fa icon="qrcode" class="fa-fw"></fa>
</router-link>
</span>
<span />
</div>
<!-- ID notice -->
<div
v-if="!activeDid"
id="noticeBeforeShare"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
>
<p class="mb-4">
<b>Note:</b> Before you can share with others or take any action, you
@ -43,10 +30,18 @@
<!-- Identity Details -->
<div
id="sectionIdentityDetails"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4"
>
<div v-if="givenName">
<h2 class="text-xl font-semibold mb-2">
<span class="whitespace-nowrap">
<router-link
:to="{ name: 'contact-qr' }"
class="bg-slate-500 text-white px-1.5 py-1 rounded-md"
>
<fa icon="qrcode" class="fa-fw text-xl"></fa>
</router-link>
</span>
{{ givenName }}
<router-link :to="{ name: 'new-edit-account' }">
<fa icon="pen" class="text-xs text-blue-500 ml-2 mb-1"></fa>
@ -160,7 +155,7 @@
<div
v-if="!loadingLimits && !endorserLimits?.nextWeekBeginDateTime"
id="noticeBeforeAnnounce"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mb-4"
class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
>
<p class="mb-4">
<b>Note:</b> Before you can publicly announce a new project or time
@ -312,7 +307,7 @@
>
If no download happened yet, click again here to download now.
</a>
<div>
<div class="mt-4">
<p>
After the download, you can save the file in your preferred storage
location.
@ -731,13 +726,17 @@ import {
IMAGE_TYPE_PROFILE,
NotificationIface,
} from "@/constants/app";
import { db, accountsDB } from "@/db/index";
import {
db,
accountsDB,
retrieveSettingsForActiveAccount,
updateAccountSettings,
} from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import {
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
MASTER_SETTINGS_KEY,
Settings,
} from "@/db/tables/settings";
import {
clearPasskeyToken,
@ -865,31 +864,29 @@ export default class AccountViewView extends Vue {
*/
async initializeState() {
await db.open();
const settings: Settings | undefined =
await db.settings.get(MASTER_SETTINGS_KEY);
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.apiServerInput = (settings?.apiServer as string) || "";
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.apiServerInput = settings.apiServer || "";
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
this.isRegistered = !!settings?.isRegistered;
this.imageServer = (settings?.imageServer as string) || "";
this.profileImageUrl = settings?.profileImageUrl as string;
this.showContactGives = !!settings?.showContactGivesInline;
this.imageServer = settings.imageServer || "";
this.profileImageUrl = settings.profileImageUrl;
this.showContactGives = !!settings.showContactGivesInline;
this.hideRegisterPromptOnNewContact =
!!settings?.hideRegisterPromptOnNewContact;
!!settings.hideRegisterPromptOnNewContact;
this.passkeyExpirationMinutes =
(settings?.passkeyExpirationMinutes as number) ??
DEFAULT_PASSKEY_EXPIRATION_MINUTES;
settings.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES;
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
this.showGeneralAdvanced = !!settings?.showGeneralAdvanced;
this.showShortcutBvc = !!settings?.showShortcutBvc;
this.warnIfProdServer = !!settings?.warnIfProdServer;
this.warnIfTestServer = !!settings?.warnIfTestServer;
this.webPushServer = (settings?.webPushServer as string) || "";
this.webPushServerInput = (settings?.webPushServer as string) || "";
this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
this.showShortcutBvc = !!settings.showShortcutBvc;
this.warnIfProdServer = !!settings.warnIfProdServer;
this.warnIfTestServer = !!settings.warnIfTestServer;
this.webPushServer = settings.webPushServer || "";
this.webPushServerInput = settings.webPushServer || "";
}
// call fn, copy text to the clipboard, then redo fn after 2 seconds
@ -1264,10 +1261,7 @@ export default class AccountViewView extends Vue {
if (!this.isRegistered) {
// the user was not known to be registered, but now they are (because we got no error) so let's record it
try {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
isRegistered: true,
});
await updateAccountSettings(did, { isRegistered: true });
this.isRegistered = true;
} catch (err) {
console.error("Got an error updating settings:", err);
@ -1478,8 +1472,7 @@ export default class AccountViewView extends Vue {
if ((error as any).response.status === 404) {
console.error("The image was already deleted:", error);
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await updateAccountSettings(this.activeDid, {
profileImageUrl: undefined,
});

10
src/views/ClaimAddRawView.vue

@ -33,8 +33,7 @@ import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { retrieveSettingsForActiveAccount } from "@/db/index";
import * as serverUtil from "@/libs/endorserServer";
import QuickNav from "@/components/QuickNav.vue";
@ -50,10 +49,9 @@ export default class ClaimAddRawView extends Vue {
claimStr = "";
async mounted() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.claimStr = (this.$route as Router).query["claim"];
try {

12
src/views/ClaimView.vue

@ -454,9 +454,8 @@ import { useClipboard } from "@vueuse/core";
import GiftedDialog from "@/components/GiftedDialog.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import * as serverUtil from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue";
@ -519,12 +518,11 @@ export default class ClaimView extends Vue {
}
async created() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.allContacts = await db.contacts.toArray();
this.isRegistered = settings?.isRegistered || false;
this.isRegistered = settings.isRegistered || false;
await accountsDB.open();
const accounts = accountsDB.accounts;

20
src/views/ConfirmGiftView.vue

@ -65,7 +65,7 @@
<!-- Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4">
<div class="block flex gap-4 overflow-hidden">
<div class="flex gap-4 overflow-hidden">
<div class="overflow-hidden">
<div class="text-sm">
<div>
@ -100,7 +100,7 @@
<router-link
:to="
'/project/' +
encodeURIComponent(giveDetails?.fulfillsPlanHandleId)
encodeURIComponent(giveDetails?.fulfillsPlanHandleId || '')
"
class="text-blue-500 mt-2 cursor-pointer"
target="_blank"
@ -121,7 +121,7 @@
<router-link
:to="
'/claim/' +
encodeURIComponent(giveDetails?.fulfillsHandleId)
encodeURIComponent(giveDetails?.fulfillsHandleId || '')
"
class="text-blue-500 mt-2 cursor-pointer"
target="_blank"
@ -129,7 +129,7 @@
This fulfills
{{
capitalizeAndInsertSpacesBeforeCapsWithAPrefix(
giveDetails.fulfillsType,
giveDetails?.fulfillsType || "",
)
}}
<fa icon="arrow-up-right-from-square" class="fa-fw" />
@ -405,10 +405,9 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import * as serverUtil from "@/libs/endorserServer";
import { displayAmount, GiveSummaryRecord } from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
@ -464,12 +463,11 @@ export default class ClaimView extends Vue {
async mounted() {
this.isLoading = true;
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.allContacts = await db.contacts.toArray();
this.isRegistered = settings?.isRegistered || false;
this.isRegistered = settings.isRegistered || false;
await accountsDB.open();
const accounts = accountsDB.accounts;

10
src/views/ContactAmountsView.vue

@ -112,9 +112,8 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import {
AgreeVerifiableCredential,
createEndorserJwtVcFromClaim,
@ -144,13 +143,12 @@ export default class ContactAmountssView extends Vue {
async created() {
try {
await db.open();
const contactDid = (this.$route as Router).query["contactDid"] as string;
this.contact = (await db.contacts.get(contactDid)) || null;
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
if (this.activeDid && this.contact) {
this.loadGives(this.activeDid, this.contact);

10
src/views/ContactGiftingView.vue

@ -77,9 +77,8 @@ import GiftedDialog from "@/components/GiftedDialog.vue";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import { NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { GiverReceiverInputInfo } from "@/libs/util";
@Component({
@ -97,10 +96,9 @@ export default class ContactGiftingView extends Vue {
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 || "";
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
// .orderBy("name") wouldn't retrieve any entries with a blank name
// .toCollection.sortBy("name") didn't sort in an order I understood

7
src/views/ContactImportView.vue

@ -16,9 +16,9 @@
Contact Import
</h1>
<span>
Note that you will have to make them visible one-by-one in the list of
Contacts.
<span class="flex justify-center">
<input type="checkbox" v-model="makeVisible" class="mr-2" />
Make my activity visible to these contacts.
</span>
<div v-if="sameCount > 0">
<span v-if="sameCount == 1"
@ -115,6 +115,7 @@ export default class ContactImportView extends Vue {
Record<string, { new: string; old: string }>
> = {}; // for existing contacts, it shows the difference between imported and existing contacts for each key
importing = false;
makeVisible = true;
sameCount = 0;
async created() {

40
src/views/ContactQRScanShowView.vue

@ -101,7 +101,7 @@ import { useClipboard } from "@vueuse/core";
import QuickNav from "@/components/QuickNav.vue";
import UserNameDialog from "@/components/UserNameDialog.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import {
@ -141,14 +141,13 @@ export default class ContactQRScanShow extends Vue {
ETHR_DID_PREFIX = ETHR_DID_PREFIX;
async created() {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.givenName = (settings?.firstName as string) || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.givenName = settings.firstName || "";
this.hideRegisterPromptOnNewContact =
!!settings?.hideRegisterPromptOnNewContact;
this.isRegistered = !!settings?.isRegistered;
!!settings.hideRegisterPromptOnNewContact;
this.isRegistered = !!settings.isRegistered;
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
@ -162,22 +161,17 @@ export default class ContactQRScanShow extends Vue {
iss: this.activeDid,
own: {
name:
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""), // lastName is deprecated, pre v 0.1.3
(settings.firstName || "") +
(settings.lastName ? ` ${settings.lastName}` : ""), // lastName is deprecated, pre v 0.1.3
publicEncKey,
profileImageUrl: settings?.profileImageUrl,
registered: settings?.isRegistered,
profileImageUrl: settings.profileImageUrl,
registered: settings.isRegistered,
},
};
if (account?.mnemonic && account?.derivationPath) {
const newDerivPath = nextDerivationPath(
account.derivationPath as string,
);
const nextPublicHex = deriveAddress(
account.mnemonic as string,
newDerivPath,
)[2];
const newDerivPath = nextDerivationPath(account.derivationPath);
const nextPublicHex = deriveAddress(account.mnemonic, newDerivPath)[2];
const nextPublicEncKey = Buffer.from(nextPublicHex, "hex");
const nextPublicEncKeyHash = sha256(nextPublicEncKey);
const nextPublicEncKeyHashBase64 =
@ -191,14 +185,14 @@ export default class ContactQRScanShow extends Vue {
viewPrefix + vcJwt;
const name =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // lastName is deprecated, pre v 0.1.3
(settings.firstName || "") +
(settings.lastName ? ` ${settings.lastName}` : ""); // lastName is deprecated, pre v 0.1.3
this.qrValue = await generateEndorserJwtForAccount(
account,
!!settings?.isRegistered,
!!settings.isRegistered,
name,
settings?.profileImageUrl as string,
settings.profileImageUrl,
);
}
}

26
src/views/ContactsView.vue

@ -292,9 +292,12 @@ import { Router } from "vue-router";
import { useClipboard } from "@vueuse/core";
import { AppString, NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import {
db,
retrieveSettingsForActiveAccount,
updateDefaultSettings,
} from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { getContactPayloadFromJwtUrl } from "@/libs/crypto";
import {
CONTACT_CSV_HEADER,
@ -352,14 +355,14 @@ export default class ContactsView extends Vue {
public async created() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
this.isRegistered = !!settings?.isRegistered;
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.isRegistered = !!settings.isRegistered;
this.showGiveNumbers = !!settings?.showContactGivesInline;
this.showGiveNumbers = !!settings.showContactGivesInline;
this.hideRegisterPromptOnNewContact =
!!settings?.hideRegisterPromptOnNewContact;
!!settings.hideRegisterPromptOnNewContact;
if (this.showGiveNumbers) {
this.loadGives();
@ -715,7 +718,7 @@ export default class ContactsView extends Vue {
text: "Do you want to register them?",
onCancel: async (stopAsking: boolean) => {
if (stopAsking) {
await db.settings.update(MASTER_SETTINGS_KEY, {
await updateDefaultSettings({
hideRegisterPromptOnNewContact: stopAsking,
});
this.hideRegisterPromptOnNewContact = stopAsking;
@ -723,7 +726,7 @@ export default class ContactsView extends Vue {
},
onNo: async (stopAsking: boolean) => {
if (stopAsking) {
await db.settings.update(MASTER_SETTINGS_KEY, {
await updateDefaultSettings({
hideRegisterPromptOnNewContact: stopAsking,
});
this.hideRegisterPromptOnNewContact = stopAsking;
@ -1067,8 +1070,7 @@ export default class ContactsView extends Vue {
private async toggleShowContactAmounts() {
const newShowValue = !this.showGiveNumbers;
try {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await updateDefaultSettings({
showContactGivesInline: newShowValue,
});
} catch (err) {

128
src/views/DIDView.vue

@ -19,14 +19,17 @@
</div>
<!-- Identity Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<div
v-if="!!contactFromDid"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"
>
<div>
<h2 class="text-xl font-semibold">
{{ contact?.name || "(no name)" }}
{{ contactFromDid?.name || "(no name)" }}
<button
@click="
contactEdit = true;
contactNewName = contact.name || '';
contactNewName = (contactFromDid?.name as string) || '';
"
title="Edit"
>
@ -49,12 +52,15 @@
>
</div>
<div class="flex justify-center mt-4">
<span v-if="contact?.profileImageUrl" class="flex justify-between">
<span
v-if="contactFromDid?.profileImageUrl"
class="flex justify-between"
>
<EntityIcon
:icon-size="96"
:profileImageUrl="contact?.profileImageUrl"
:profileImageUrl="contactFromDid?.profileImageUrl"
class="inline-block align-text-bottom border border-slate-300 rounded"
@click="showLargeIdenticonUrl = contact?.profileImageUrl"
@click="showLargeIdenticonUrl = contactFromDid?.profileImageUrl"
/>
</span>
</div>
@ -63,17 +69,21 @@
<div v-if="activeDid" class="flex justify-between">
<div>
<button
v-if="contact?.seesMe && contact.did !== activeDid"
v-if="
contactFromDid?.seesMe && contactFromDid.did !== activeDid
"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="confirmSetVisibility(contact, false)"
@click="confirmSetVisibility(contactFromDid, false)"
title="They can see you"
>
<fa icon="eye" class="fa-fw" />
</button>
<button
v-else-if="!contact?.seesMe && contact?.did !== activeDid"
v-else-if="
!contactFromDid?.seesMe && contactFromDid?.did !== activeDid
"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="confirmSetVisibility(contact, true)"
@click="confirmSetVisibility(contactFromDid, true)"
title="They cannot see you"
>
<fa icon="eye-slash" class="fa-fw" />
@ -83,9 +93,9 @@
<button
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
@click="checkVisibility(contact)"
@click="checkVisibility(contactFromDid)"
title="Check Visibility"
v-if="contact?.did !== activeDid"
v-if="contactFromDid?.did !== activeDid"
>
<fa icon="rotate" class="fa-fw" />
</button>
@ -94,13 +104,13 @@
</div>
<button
@click="confirmRegister(contact)"
@click="confirmRegister(contactFromDid)"
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
v-if="contact?.did !== activeDid"
v-if="contactFromDid?.did !== activeDid"
title="Registration"
>
<fa
v-if="contact?.registered"
v-if="contactFromDid?.registered"
icon="person-circle-check"
class="fa-fw"
/>
@ -111,14 +121,14 @@
</div>
<button
@click="confirmDeleteContact(contact)"
@click="confirmDeleteContact(contactFromDid)"
class="text-sm uppercase bg-gradient-to-b from-rose-500 to-rose-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white ml-6 mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
title="Delete"
>
<fa icon="trash-can" class="fa-fw" />
</button>
</div>
<div v-if="!contact?.profileImageUrl">
<div v-if="!contactFromDid?.profileImageUrl">
<div>Auto-Generated Icon</div>
<div class="flex justify-center">
<EntityIcon
@ -150,6 +160,15 @@
</div>
</div>
</div>
<div v-else class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<!-- !contactFromDid -->
<div>
<h2 class="text-xl font-semibold">
{{ isMyDid ? "You" : "(no name)" }}
</h2>
</div>
</div>
<div v-if="contactEdit" class="dialog-overlay">
<div class="dialog">
<h1 class="text-xl font-bold text-center mb-4">Edit Name</h1>
@ -222,7 +241,8 @@
v-if="!isLoading && claims.length === 0"
class="flex justify-center mt-4"
>
<span>They are in no claims visible to you.</span>
<span v-if="isMyDid">You have no claims yet.</span>
<span v-else>They are in no claims visible to you.</span>
</div>
</section>
</template>
@ -237,9 +257,9 @@ import QuickNav from "@/components/QuickNav.vue";
import InfiniteScroll from "@/components/InfiniteScroll.vue";
import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { BoundingBox } from "@/db/tables/settings";
import {
capitalizeAndInsertSpacesBeforeCaps,
didInfoForContact,
@ -270,15 +290,15 @@ export default class DIDView extends Vue {
yaml = yaml;
activeDid = "";
allMyDids: Array<string> = [];
apiServer = "";
claims: Array<GenericCredWrapper<GenericVerifiableCredential>> = [];
contact: Contact;
contactFromDid?: Contact;
contactEdit = false;
contactNewName?: string;
contactNewName: string = "";
contactYaml = "";
hitEnd = false;
isLoading = false;
isMyDid = false;
searchBox: { name: string; bbox: BoundingBox } | null = null;
showDidDetails = false;
showLargeIdenticonId?: string;
@ -290,38 +310,28 @@ export default class DIDView extends Vue {
displayAmount = displayAmount;
async mounted() {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
const pathParam = window.location.pathname.substring("/did/".length);
let theContact: Contact | undefined;
if (pathParam) {
this.viewingDid = decodeURIComponent(pathParam);
theContact = await db.contacts.get(this.viewingDid);
}
if (theContact) {
this.contact = theContact;
} else {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "No valid claim ID was provided.",
},
-1,
);
return;
this.contactFromDid = await db.contacts.get(this.viewingDid);
if (this.contactFromDid) {
this.contactYaml = yaml.dump(this.contactFromDid);
}
await this.loadClaimsAbout();
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
for (const account of allAccounts) {
if (account.did === this.viewingDid) {
this.isMyDid = true;
break;
}
}
}
this.contactYaml = yaml.dump(this.contact);
await this.loadClaimsAbout();
await accountsDB.open();
const allAccounts = await accountsDB.accounts.toArray();
this.allMyDids = allAccounts.map((acc) => acc.did);
}
/**
@ -377,7 +387,7 @@ export default class DIDView extends Vue {
title: "Register",
text:
"Are you sure you want to register " +
libsUtil.nameForContact(this.contact, false) +
libsUtil.nameForContact(this.contactFromDid, false) +
(contact.registered
? " -- especially since they are already marked as registered"
: "") +
@ -558,9 +568,21 @@ export default class DIDView extends Vue {
}
private async onClickSaveName(newName: string) {
this.contact.name = newName;
if (!this.contactFromDid) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Not A Contact",
text: "First add this on the contact page, then you can edit here.",
},
5000,
);
return;
}
this.contactFromDid.name = newName;
return db.contacts
.update(this.contact.did, { name: newName })
.update(this.contactFromDid.did, { name: newName })
.then(() => (this.contactEdit = false));
}

13
src/views/DiscoverView.vue

@ -146,9 +146,9 @@ import InfiniteScroll from "@/components/InfiniteScroll.vue";
import ProjectIcon from "@/components/ProjectIcon.vue";
import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { BoundingBox } from "@/db/tables/settings";
import { didInfo, getHeaders, PlanData } from "@/libs/endorserServer";
@Component({
@ -179,11 +179,10 @@ export default class DiscoverView extends Vue {
didInfo = didInfo;
async mounted() {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.searchBox = settings?.searchBoxes?.[0] || null;
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = (settings.activeDid as string) || "";
this.apiServer = (settings.apiServer as string) || "";
this.searchBox = settings.searchBoxes?.[0] || null;
this.allContacts = await db.contacts.toArray();

10
src/views/GiftedDetailsView.vue

@ -181,8 +181,7 @@ import ImageMethodDialog from "@/components/ImageMethodDialog.vue";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import {
createAndSubmitGive,
didInfo,
@ -319,10 +318,9 @@ export default class GiftedDetails extends Vue {
}
try {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
let allContacts: Contact[] = [];
let allMyDids: string[] = [];

50
src/views/HomeView.vue

@ -84,11 +84,11 @@
id="noticeSomeoneMustRegisterYou"
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
>
<!-- activeDid && !isRegistered -->
<!-- !isCreatingIdentifier && !isRegistered -->
To share, someone must register you.
<div class="block text-center">
<button
@click="showNameDialog()"
@click="showNameThenIdDialog()"
class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
>
Show them {{ PASSKEYS_ENABLED ? "default" : "your" }} identifier
@ -107,7 +107,7 @@
</div>
<div v-else id="sectionRecordSomethingGiven">
<!-- activeDid && isRegistered -->
<!-- !isCreatingIdentifier && isRegistered -->
<!-- show the actions for recognizing a give -->
<div class="flex justify-between">
@ -325,13 +325,17 @@ import {
NotificationIface,
PASSKEYS_ENABLED,
} from "@/constants/app";
import { db, accountsDB } from "@/db/index";
import {
db,
accountsDB,
updateAccountSettings,
retrieveSettingsForActiveAccount,
} from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import {
BoundingBox,
isAnyFeedFilterOn,
MASTER_SETTINGS_KEY,
Settings,
} from "@/db/tables/settings";
import {
contactForDid,
@ -420,18 +424,17 @@ export default class HomeView extends Vue {
this.allMyDids = [newDid];
}
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
this.allContacts = await db.contacts.toArray();
this.feedLastViewedClaimId = settings?.lastViewedClaimId;
this.givenName = settings?.firstName || "";
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
this.isRegistered = !!settings?.isRegistered;
this.searchBoxes = settings?.searchBoxes || [];
this.showShortcutBvc = !!settings?.showShortcutBvc;
this.feedLastViewedClaimId = settings.lastViewedClaimId;
this.givenName = settings.firstName || "";
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isRegistered = !!settings.isRegistered;
this.searchBoxes = settings.searchBoxes || [];
this.showShortcutBvc = !!settings.showShortcutBvc;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
@ -444,9 +447,7 @@ export default class HomeView extends Vue {
this.activeDid,
);
if (resp.status === 200) {
// we just needed to know that they're registered
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await updateAccountSettings(this.activeDid, {
isRegistered: true,
});
this.isRegistered = true;
@ -495,10 +496,9 @@ export default class HomeView extends Vue {
// only called when a setting was changed
async reloadFeedOnChange() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.isFeedFilteredByVisible = !!settings?.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings?.filterFeedByNearby;
const settings = await retrieveSettingsForActiveAccount();
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isAnyFeedFilterOn = isAnyFeedFilterOn(settings);
this.feedData = [];
@ -555,7 +555,7 @@ export default class HomeView extends Vue {
// This has indeed proven problematic. See loadMoreGives
// We should display it immediately and then get the plan later.
const plan = await getPlanFromCache(
record.fulfillsPlanHandleId,
record.fulfillsPlanHandleId || "",
this.axios,
this.apiServer,
this.activeDid,
@ -782,7 +782,7 @@ export default class HomeView extends Vue {
return known ? "text-slate-500" : "text-slate-100";
}
showNameDialog() {
showNameThenIdDialog() {
if (!this.givenName) {
(this.$refs.userNameDialog as UserNameDialog).open(() => {
this.promptForShareMethod();

13
src/views/IdentitySwitcherView.vue

@ -102,8 +102,8 @@ import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { NotificationIface } from "@/constants/app";
import { db, accountsDB } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { db, accountsDB, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import QuickNav from "@/components/QuickNav.vue";
@Component({ components: { QuickNav } })
@ -118,11 +118,10 @@ export default class IdentitySwitcherView extends Vue {
async created() {
try {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
this.apiServerInput = settings?.apiServer || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.apiServerInput = settings.apiServer || "";
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();

11
src/views/NewEditAccountView.vue

@ -47,8 +47,8 @@
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component({
components: {},
@ -58,11 +58,10 @@ export default class NewEditAccountView extends Vue {
// 'created' hook runs when the Vue instance is first created
async created() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
const settings = await retrieveSettingsForActiveAccount();
this.givenName =
(settings?.firstName || "") +
(settings?.lastName ? ` ${settings.lastName}` : ""); // deprecated, pre v 0.1.3
(settings.firstName || "") +
(settings.lastName ? ` ${settings.lastName}` : ""); // deprecated, pre v 0.1.3
}
async onClickSaveChanges() {

10
src/views/NewEditProjectView.vue

@ -185,8 +185,7 @@ import { Router } from "vue-router";
import ImageMethodDialog from "@/components/ImageMethodDialog.vue";
import QuickNav from "@/components/QuickNav.vue";
import { DEFAULT_IMAGE_API_SERVER, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accountsDB, retrieveSettingsForActiveAccount } from "@/db/index";
import {
createEndorserJwtVcFromClaim,
getHeaders,
@ -234,10 +233,9 @@ export default class NewEditProjectView extends Vue {
await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.projectId = (this.$route as Router).query["projectId"] || "";

10
src/views/OfferDetailsView.vue

@ -181,8 +181,7 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import {
createAndSubmitOffer,
didInfo,
@ -296,10 +295,9 @@ export default class OfferDetailsView extends Vue {
this.prevCredToEdit?.claim?.validThrough || this.validThroughDateInput;
try {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
let allContacts: Contact[] = [];
let allMyDids: string[] = [];

12
src/views/ProjectViewView.vue

@ -431,10 +431,9 @@ import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import ProjectIcon from "@/components/ProjectIcon.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import * as libsUtil from "@/libs/util";
import {
BLANK_GENERIC_SERVER_RECORD,
@ -494,12 +493,11 @@ export default class ProjectViewView extends Vue {
serverUtil = serverUtil;
async created() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.allContacts = await db.contacts.toArray();
this.isRegistered = !!settings?.isRegistered;
this.isRegistered = !!settings.isRegistered;
await accountsDB.open();
const accounts = accountsDB.accounts;

66
src/views/ProjectsView.vue

@ -208,6 +208,15 @@
/>
button. You'll never know until you try.
</div>
<div v-else>
<button
@click="showNameThenIdDialog()"
class="text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
>
Get someone to onboard you.
</button>
<UserNameDialog ref="userNameDialog" />
</div>
</div>
<ul id="listProjects" class="border-t border-slate-300">
<li
@ -247,8 +256,7 @@ import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import * as libsUtil from "@/libs/util";
import InfiniteScroll from "@/components/InfiniteScroll.vue";
import QuickNav from "@/components/QuickNav.vue";
@ -263,9 +271,17 @@ import {
} from "@/libs/endorserServer";
import EntityIcon from "@/components/EntityIcon.vue";
import { Contact } from "@/db/tables/contacts";
import UserNameDialog from "@/components/UserNameDialog.vue";
@Component({
components: { EntityIcon, InfiniteScroll, QuickNav, ProjectIcon, TopMessage },
components: {
EntityIcon,
InfiniteScroll,
QuickNav,
ProjectIcon,
TopMessage,
UserNameDialog,
},
})
export default class ProjectsView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
@ -280,11 +296,12 @@ export default class ProjectsView extends Vue {
allContacts: Array<Contact> = [];
allMyDids: Array<string> = [];
apiServer = "";
projects: PlanData[] = [];
givenName = "";
isLoading = false;
isRegistered = false;
offers: OfferSummaryRecord[] = [];
projectNameFromHandleId: Record<string, string> = {}; // mapping from handleId to description
projects: PlanData[] = [];
showOffers = true;
showProjects = false;
@ -293,11 +310,11 @@ export default class ProjectsView extends Vue {
async mounted() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
this.isRegistered = !!settings?.isRegistered;
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
this.isRegistered = !!settings.isRegistered;
this.givenName = settings.firstName || "";
this.allContacts = await db.contacts.toArray();
@ -497,6 +514,37 @@ export default class ProjectsView extends Vue {
await this.offerDataLoader(url);
}
showNameThenIdDialog() {
if (!this.givenName) {
(this.$refs.userNameDialog as UserNameDialog).open(() => {
this.promptForShareMethod();
});
} else {
this.promptForShareMethod();
}
}
promptForShareMethod() {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Are you nearby with cameras?",
text: "If so, we'll use those with QR codes to share.",
onCancel: async () => {},
onNo: async () => {
(this.$router as Router).push({ name: "share-my-contact-info" });
},
onYes: async () => {
(this.$router as Router).push({ name: "contact-qr" });
},
noText: "we will share another way",
yesText: "we are nearby with cameras",
},
-1,
);
}
public computedOfferTabClassNames() {
return {
"inline-block": true,

10
src/views/QuickActionBvcBeginView.vue

@ -72,7 +72,7 @@ import { Component, Vue } from "vue-facing-decorator";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { retrieveSettingsForActiveAccount } from "@/db/index";
import {
BVC_MEETUPS_PROJECT_CLAIM_ID,
bvcMeetingJoinClaim,
@ -80,7 +80,6 @@ import {
createAndSubmitGive,
} from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
@Component({
components: {
@ -117,10 +116,9 @@ export default class QuickActionBvcBeginView extends Vue {
}
async record() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
const activeDid = settings?.activeDid || "";
const apiServer = settings?.apiServer || "";
const settings = await retrieveSettingsForActiveAccount();
const activeDid = settings.activeDid || "";
const apiServer = settings.apiServer || "";
try {
const hoursNum = libsUtil.numberOrZero(this.hoursStr);

10
src/views/QuickActionBvcEndView.vue

@ -144,9 +144,8 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import {
BVC_MEETUPS_PROJECT_CLAIM_ID,
claimSpecialDescription,
@ -182,10 +181,9 @@ export default class QuickActionBvcBeginView extends Vue {
someoneGave = false;
async created() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.apiServer = settings?.apiServer || "";
this.activeDid = settings?.activeDid || "";
const settings = await retrieveSettingsForActiveAccount();
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
this.allContacts = await db.contacts.toArray();
}

7
src/views/SearchAreaView.vue

@ -109,7 +109,7 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { BoundingBox, MASTER_SETTINGS_KEY } from "@/db/tables/settings";
const DEFAULT_LAT_LONG_DIFF = 0.01;
@ -142,9 +142,8 @@ export default class DiscoverView extends Vue {
searchBox: { name: string; bbox: BoundingBox } | null = null;
async mounted() {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.searchBox = settings?.searchBoxes?.[0] || null;
const settings = await retrieveSettingsForActiveAccount();
this.searchBox = settings.searchBoxes?.[0] || null;
this.resetLatLong();
}

8
src/views/SeedBackupView.vue

@ -105,9 +105,8 @@ import { useClipboard } from "@vueuse/core";
import QuickNav from "@/components/QuickNav.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { accountsDB, retrieveSettingsForActiveAccount } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component({ components: { QuickNav } })
export default class SeedBackupView extends Vue {
@ -122,9 +121,8 @@ export default class SeedBackupView extends Vue {
// 'created' hook runs when the Vue instance is first created
async created() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const activeDid = settings?.activeDid || "";
const settings = await retrieveSettingsForActiveAccount();
const activeDid = settings.activeDid || "";
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();

14
src/views/ShareMyContactInfoView.vue

@ -49,8 +49,7 @@ import { useClipboard } from "@vueuse/core";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import { generateEndorserJwtForAccount } from "@/libs/endorserServer";
@Component({
@ -60,12 +59,11 @@ export default class ShareMyContactInfoView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
async onClickShare() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
const activeDid = settings?.activeDid || "";
const givenName = settings?.firstName || "";
const isRegistered = !!settings?.isRegistered;
const profileImageUrl = settings?.profileImageUrl || "";
const settings = await retrieveSettingsForActiveAccount();
const activeDid = settings.activeDid || "";
const givenName = settings.firstName || "";
const isRegistered = !!settings.isRegistered;
const profileImageUrl = settings.profileImageUrl || "";
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();

7
src/views/SharedPhotoView.vue

@ -75,7 +75,7 @@ import {
IMAGE_TYPE_PROFILE,
NotificationIface,
} from "@/constants/app";
import { db } from "@/db/index";
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import { base64ToBlob, SHARED_PHOTO_BASE64_KEY } from "@/libs/util";
@ -94,9 +94,8 @@ export default class SharedPhotoView extends Vue {
// 'created' hook runs when the Vue instance is first created
async mounted() {
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = settings?.activeDid as string;
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid;
const temp = await db.temp.get(SHARED_PHOTO_BASE64_KEY);
const imageB64 = temp?.blobB64 as string;

8
src/views/StartView.vue

@ -92,8 +92,7 @@ import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import { AppString, PASSKEYS_ENABLED } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accountsDB, retrieveSettingsForActiveAccount } from "@/db/index";
import { registerSaveAndActivatePasskey } from "@/libs/util";
@Component({
@ -106,9 +105,8 @@ export default class StartView extends Vue {
numAccounts = 0;
async mounted() {
await db.open();
const settings = (await db.settings.get(MASTER_SETTINGS_KEY)) as Settings;
this.givenName = settings?.firstName || "";
const settings = await retrieveSettingsForActiveAccount();
this.givenName = settings.firstName || "";
await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();

10
src/views/TestView.vue

@ -247,8 +247,7 @@ import { Router } from "vue-router";
import QuickNav from "@/components/QuickNav.vue";
import { AppString, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accountsDB, db, retrieveSettingsForActiveAccount } from "@/db/index";
import * as vcLib from "@/libs/crypto/vc";
import {
PeerSetup,
@ -291,10 +290,9 @@ export default class Help extends Vue {
userName?: string;
async mounted() {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = (settings?.activeDid as string) || "";
this.userName = settings?.firstName as string;
const settings = await retrieveSettingsForActiveAccount();
this.activeDid = settings.activeDid || "";
this.userName = settings.firstName;
await accountsDB.open();
const account: { identity?: string } | undefined = await accountsDB.accounts

31
test-playwright/00-noid-tests.spec.ts

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test';
import { generateEthrUser, importUser } from './testUtils';
import { deleteContact, generateEthrUser, importUser } from './testUtils';
test('Check activity feed', async ({ page }) => {
// Load app homepage
@ -37,6 +37,17 @@ test('Check no-ID messaging in account', async ({ page }) => {
await expect(page.locator('#sectionIdentityDetails code.truncate')).toBeEmpty();
});
test('Check ability to share contact', async ({ page }) => {
// Load Discover view
await page.goto('./discover');
// Check that initial 10 projects have been loaded
await page.locator('ul#listDiscoverResults li.border-b:nth-child(10)');
// Scroll down a bit to trigger loading additional projects
await page.locator('ul#listDiscoverResults li.border-b:nth-child(20)').scrollIntoViewIfNeeded();
});
test('Check ID generation', async ({ page }) => {
// Load Account view
await page.goto('./account');
@ -83,7 +94,7 @@ test('Check setting name & sharing info', async ({ page }) => {
await expect(page.getByText('your contacts')).toBeVisible();
});
test('Confirm usage of test API (may fail if you are running your own Time Safari)', async ({ page }, testInfo) => {
test('Confirm test API setting (may fail if you are running your own Time Safari)', async ({ page }, testInfo) => {
// Load account view
await page.goto('./account');
await page.getByRole('heading', { name: 'Advanced' }).click();
@ -112,4 +123,20 @@ test('Check User 0 can register a random person', async ({ page }) => {
// now ensure that alert goes away
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert
await expect(page.getByText('That gift was recorded.')).toBeHidden();
// now delete the contact to test that pages still do reasonable things
await deleteContact(page, newDid);
// go the activity page for this new person
await page.goto('./did/' + encodeURIComponent(newDid));
let error;
try {
await page.waitForSelector('div[role="alert"]', { timeout: 2000 });
error = new Error('Error alert should not show.');
} catch (error) {
// success
} finally {
if (error) {
throw error;
}
}
});

32
test-playwright/40-add-contact.spec.ts

@ -22,7 +22,7 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
const finalTitle = standardTitle + finalRandomString;
// Contact name
const contactName = 'Contact #111';
const contactName = 'Contact #000 renamed';
// Import user 01
await importUser(page, '01');
@ -45,6 +45,7 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
await expect(page.locator('div.dialog-overlay > div.dialog').filter({ hasText: 'Edit Name' })).toBeVisible();
await page.getByPlaceholder('Name', { exact: true }).fill(contactName);
await page.locator('.dialog > .flex > button').first().click();
// await page.locator('.dialog > .flex > button').first().click(); // close alert
// Confirm that home shows contact in "Record Something…"
await page.goto('./');
@ -59,6 +60,9 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
// Refresh home view and check gift
await page.goto('./');
// Firefox complains on load the initial feed here when we use the test server.
// It may be similar to the CORS problem below.
await page.locator('li').filter({ hasText: finalTitle }).locator('a').click();
await expect(page.getByRole('heading', { name: 'Verifiable Claim Details' })).toBeVisible();
await expect(page.getByText(finalTitle, { exact: true })).toBeVisible();
@ -88,6 +92,24 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
await expect(page.locator('div[role="alert"]')).toBeVisible();
});
test('Without being registered, add contacts without registration', async ({ page, context }) => {
await page.goto('./account');
// wait until the DID shows on the page in the 'did' element
const didElem = await page.getByTestId('didWrapper').locator('code');
const newDid = await didElem.innerText();
expect(newDid.trim()).toEqual('');
// Add new contact without registering
await page.goto('./contacts');
await page.getByPlaceholder('URL or DID, Name, Public Key').fill('did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39, User #111');
await page.locator('button > svg.fa-plus').click();
await expect(page.locator('div[role="alert"] span:has-text("Contact Added")')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
// wait for the alert to disappear, which also ensures that there is no "Register" button waiting
await expect(page.locator('div[role="alert"]')).toBeHidden();
});
test('Add contact, copy details, delete, and import various ways', async ({ page, context }) => {
await importUser(page, '00');
@ -117,6 +139,7 @@ test('Add contact, copy details, delete, and import various ways', async ({ page
await page.getByTestId('contactCheckAllTop').click();
await page.getByTestId('copySelectedContactsButtonTop').click();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert
await expect(page.locator('div[role="alert"]')).toBeHidden();
// I would prefer to copy from the clipboard, but the recommended approaches don't work.
// this seems to fail in non-chromium browsers
//await context.grantPermissions(['clipboard-read', 'clipboard-write']);
@ -125,12 +148,19 @@ test('Add contact, copy details, delete, and import various ways', async ({ page
// see contact details on the second contact
await page.getByTestId('contactListItem').nth(1).locator('a').click();
await page.getByRole('heading', { name: 'Identifier Details' }).isVisible();
// remove contact
await page.locator('button > svg.fa-trash-can').click();
await page.locator('div[role="alert"] button:has-text("Yes")').click();
// for some reason, .isHidden() (without expect) doesn't work
await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden();
// Firefox has a problem when we run this against the test server. It doesn't load the feed.
// It says there's a CORS problem; maybe it's more strict than the other browsers.
// It works when we set the config to use a local server.
// Seems like we hit a similar problem above.
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss alert
await expect(page.locator('div[role="alert"]')).toBeHidden();
// go to the contacts page and paste the copied contact details
await page.goto('./contacts');

22
test-playwright/testUtils.ts

@ -40,6 +40,22 @@ export async function switchToUser(page: Page, did: string): Promise<void> {
await page.getByRole('code', { name: did }).click();
}
function createContactName(did: string): string {
return "User " + did.slice(11, 14);
}
export async function deleteContact(page: Page, did: string): Promise<void> {
await page.goto('./contacts');
const contactName = createContactName(did);
// go to the detail page for this contact
await page.locator(`li[data-testid="contactListItem"] h2:has-text("${contactName}") + a`).click();
// delete the contact
await page.locator('button > svg.fa-trash-can').click();
await page.locator('div[role="alert"] button:has-text("Yes")').click();
// for some reason, .isHidden() (without expect) doesn't work
await expect(page.locator('div[role="alert"] button:has-text("Yes")')).toBeHidden();
}
// Generate a new random user and register them.
// Note that this makes 000 the active user. Use switchToUser to switch to this DID.
export async function generateEthrUser(page: Page): Promise<string> {
@ -55,10 +71,10 @@ export async function generateEthrUser(page: Page): Promise<string> {
await importUser(page, '000'); // switch to user 000
await page.goto('./contacts');
const threeChars = newDid.slice(11, 14);
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${newDid}, User ${threeChars}`);
const contactName = createContactName(newDid);
await page.getByPlaceholder('URL or DID, Name, Public Key').fill(`${newDid}, ${contactName}`);
await page.locator('button > svg.fa-plus').click();
await page.locator('li', { hasText: threeChars }).click();
await page.locator('li', { hasText: contactName }).click();
// register them
await page.locator('div[role="alert"] button:has-text("Yes")').click();
// wait for it to disappear because the next steps may depend on alerts being gone

Loading…
Cancel
Save