forked from jsnbuchanan/crowd-funder-for-time-pwa
add registration inside contact import, with flag to hide it
This commit is contained in:
46
src/App.vue
46
src/App.vue
@@ -146,13 +146,19 @@
|
|||||||
move="transition duration-500"
|
move="transition duration-500"
|
||||||
move-delay="delay-300"
|
move-delay="delay-300"
|
||||||
>
|
>
|
||||||
|
<!-- see NotificationIface in constants/app.ts -->
|
||||||
<div
|
<div
|
||||||
v-for="notification in notifications"
|
v-for="notification in notifications"
|
||||||
:key="notification.id"
|
:key="notification.id"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<!-- type "confirm" will post a message and, with onYes function, show a "Yes" button to call that function -->
|
<!--
|
||||||
|
Type of "confirm" will post a message.
|
||||||
|
With onYes function, show a "Yes" button to call that function.
|
||||||
|
With onNo function, show a "No" button to call that function,
|
||||||
|
and pass it state of "askAgain" field shown if you set promptToStopAsking.
|
||||||
|
-->
|
||||||
<div
|
<div
|
||||||
v-if="notification.type === 'confirm'"
|
v-if="notification.type === 'confirm'"
|
||||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
||||||
@@ -180,16 +186,49 @@
|
|||||||
<button
|
<button
|
||||||
v-if="notification.onNo"
|
v-if="notification.onNo"
|
||||||
@click="
|
@click="
|
||||||
notification.onNo();
|
notification.onNo(stopAsking);
|
||||||
close(notification.id);
|
close(notification.id);
|
||||||
|
stopAsking = false; // reset value
|
||||||
"
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2"
|
class="block w-full text-center text-md font-bold uppercase bg-yellow-600 text-white px-2 py-2 rounded-md mb-2"
|
||||||
>
|
>
|
||||||
No
|
No
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<label
|
||||||
|
v-if="notification.promptToStopAsking && notification.onNo"
|
||||||
|
for="toggleStopAsking"
|
||||||
|
class="flex items-center justify-between cursor-pointer my-4"
|
||||||
|
@click="stopAsking = !stopAsking"
|
||||||
|
>
|
||||||
|
<!-- label -->
|
||||||
|
<span class="ml-2">... and do not ask again.</span>
|
||||||
|
<!-- toggle -->
|
||||||
|
<div class="relative ml-2">
|
||||||
|
<!-- input -->
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
v-model="stopAsking"
|
||||||
|
name="stopAsking"
|
||||||
|
class="sr-only"
|
||||||
|
/>
|
||||||
|
<!-- line -->
|
||||||
|
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
||||||
|
<!-- dot -->
|
||||||
|
<div
|
||||||
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="close(notification.id)"
|
@click="
|
||||||
|
notification.onCancel
|
||||||
|
? notification.onCancel(stopAsking)
|
||||||
|
: null;
|
||||||
|
close(notification.id);
|
||||||
|
stopAsking = false; // reset value
|
||||||
|
"
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
||||||
>
|
>
|
||||||
{{ notification.onYes ? "Cancel" : "Close" }}
|
{{ notification.onYes ? "Cancel" : "Close" }}
|
||||||
@@ -373,6 +412,7 @@ import { sendTestThroughPushServer } from "@/libs/util";
|
|||||||
export default class App extends Vue {
|
export default class App extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
stopAsking = false;
|
||||||
b64 = "";
|
b64 = "";
|
||||||
hourAm = true;
|
hourAm = true;
|
||||||
hourInput = "8";
|
hourInput = "8";
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ export interface NotificationIface {
|
|||||||
type: string; // "toast" | "info" | "success" | "warning" | "danger"
|
type: string; // "toast" | "info" | "success" | "warning" | "danger"
|
||||||
title: string;
|
title: string;
|
||||||
text: string;
|
text: string;
|
||||||
onNo?: () => Promise<void>;
|
onCancel?: (stopAsking: boolean) => Promise<void>;
|
||||||
|
onNo?: (stopAsking: boolean) => Promise<void>;
|
||||||
onYes?: () => Promise<void>;
|
onYes?: () => Promise<void>;
|
||||||
|
promptToStopAsking?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export type Settings = {
|
|||||||
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden
|
filterFeedByVisible?: boolean; // filter by visible users ie. anyone not hidden
|
||||||
|
|
||||||
firstName?: string; // user's full name
|
firstName?: string; // user's full name
|
||||||
|
hideRegisterPromptOnNewContact?: boolean;
|
||||||
isRegistered?: boolean;
|
isRegistered?: boolean;
|
||||||
lastName?: string; // deprecated - put all names in firstName
|
lastName?: string; // deprecated - put all names in firstName
|
||||||
lastNotifiedClaimId?: string;
|
lastNotifiedClaimId?: string;
|
||||||
@@ -36,7 +37,7 @@ export type Settings = {
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
showContactGivesInline?: boolean; // Display contact inline or not
|
showContactGivesInline?: boolean; // Display contact inline or not
|
||||||
showShortcutBvc?: boolean; // Show shortcut for BVC actions
|
showShortcutBvc?: boolean; // Show shortcut for Bountiful Voluntaryist Community actions
|
||||||
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
|
vapid?: string; // VAPID (Voluntary Application Server Identification) field for web push
|
||||||
warnIfProdServer?: boolean; // Warn if using a production server
|
warnIfProdServer?: boolean; // Warn if using a production server
|
||||||
warnIfTestServer?: boolean; // Warn if using a testing server
|
warnIfTestServer?: boolean; // Warn if using a testing server
|
||||||
|
|||||||
@@ -911,6 +911,65 @@ export const bvcMeetingJoinClaim = (did: string, startTime: string) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function register(
|
||||||
|
activeDid: string,
|
||||||
|
apiServer: string,
|
||||||
|
axios: Axios,
|
||||||
|
contact: Contact,
|
||||||
|
) {
|
||||||
|
const identity = await getIdentity(activeDid);
|
||||||
|
|
||||||
|
const vcClaim: RegisterVerifiableCredential = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "RegisterAction",
|
||||||
|
agent: { identifier: identity.did },
|
||||||
|
object: SERVICE_ID,
|
||||||
|
participant: { identifier: contact.did },
|
||||||
|
};
|
||||||
|
// Make a payload for the claim
|
||||||
|
const vcPayload = {
|
||||||
|
vc: {
|
||||||
|
"@context": ["https://www.w3.org/2018/credentials/v1"],
|
||||||
|
type: ["VerifiableCredential"],
|
||||||
|
credentialSubject: vcClaim,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// Create a signature using private key of identity
|
||||||
|
if (identity.keys[0].privateKeyHex == null) {
|
||||||
|
return { error: "Private key not found." };
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
const privateKeyHex: string = identity.keys[0].privateKeyHex!;
|
||||||
|
const signer = await SimpleSigner(privateKeyHex);
|
||||||
|
const alg = undefined;
|
||||||
|
// Create a JWT for the request
|
||||||
|
const vcJwt: string = await didJwt.createJWT(vcPayload, {
|
||||||
|
alg: alg,
|
||||||
|
issuer: identity.did,
|
||||||
|
signer: signer,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make the xhr request payload
|
||||||
|
const payload = JSON.stringify({ jwtEncoded: vcJwt });
|
||||||
|
const url = apiServer + "/api/v2/claim";
|
||||||
|
const headers = await getHeaders(identity);
|
||||||
|
|
||||||
|
const resp = await axios.post(url, payload, { headers });
|
||||||
|
if (resp.data?.success?.handleId) {
|
||||||
|
return { success: true };
|
||||||
|
} else if (resp.data?.success?.embeddedRecordError) {
|
||||||
|
let message =
|
||||||
|
"There was some problem with the registration and so it may not be complete.";
|
||||||
|
if (typeof resp.data.success.embeddedRecordError == "string") {
|
||||||
|
message += " " + resp.data.success.embeddedRecordError;
|
||||||
|
}
|
||||||
|
return { error: message };
|
||||||
|
} else {
|
||||||
|
console.error(resp);
|
||||||
|
return { error: "Got a server error when registering." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function setVisibilityUtil(
|
export async function setVisibilityUtil(
|
||||||
activeDid: string,
|
activeDid: string,
|
||||||
apiServer: string,
|
apiServer: string,
|
||||||
@@ -931,7 +990,6 @@ export async function setVisibilityUtil(
|
|||||||
try {
|
try {
|
||||||
const resp = await axios.post(url, payload, { headers });
|
const resp = await axios.post(url, payload, { headers });
|
||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
contact.seesMe = visibility;
|
|
||||||
db.contacts.update(contact.did, { seesMe: visibility });
|
db.contacts.update(contact.did, { seesMe: visibility });
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -532,25 +532,47 @@
|
|||||||
<span class="text-sm">{{ DEFAULT_IMAGE_API_SERVER }}</span>
|
<span class="text-sm">{{ DEFAULT_IMAGE_API_SERVER }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<label
|
||||||
|
for="toggleHideRegisterPromptOnNewContact"
|
||||||
|
class="flex items-center justify-between cursor-pointer mt-4"
|
||||||
|
@click="toggleHideRegisterPromptOnNewContact()"
|
||||||
|
>
|
||||||
|
<!-- label -->
|
||||||
|
<span class="text-slate-500 text-sm font-bold">
|
||||||
|
Hide Register Prompt on New Contact
|
||||||
|
</span>
|
||||||
|
<!-- toggle -->
|
||||||
|
<div class="relative ml-2">
|
||||||
|
<!-- input -->
|
||||||
|
<input type="checkbox" v-model="hideRegisterPromptOnNewContact" class="sr-only" />
|
||||||
|
<!-- line -->
|
||||||
|
<div class="block bg-slate-500 w-14 h-8 rounded-full" />
|
||||||
|
<!-- dot -->
|
||||||
|
<div
|
||||||
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
<label
|
<label
|
||||||
for="toggleShowShortcutBvc"
|
for="toggleShowShortcutBvc"
|
||||||
class="flex items-center justify-between cursor-pointer mt-4"
|
class="flex items-center justify-between cursor-pointer mt-4"
|
||||||
@click="toggleShowShortcutBvc"
|
@click="toggleShowShortcutBvc"
|
||||||
>
|
>
|
||||||
<!-- label -->
|
<!-- label -->
|
||||||
<span class="text-slate-500 text-sm font-bold"
|
<span class="text-slate-500 text-sm font-bold">
|
||||||
>Show BVC Shortcut on Home Page</span
|
Show BVC Shortcut on Home Page
|
||||||
>
|
</span>
|
||||||
<!-- toggle -->
|
<!-- toggle -->
|
||||||
<div class="relative ml-2">
|
<div class="relative ml-2">
|
||||||
<!-- input -->
|
<!-- input -->
|
||||||
<input type="checkbox" v-model="showShortcutBvc" class="sr-only" />
|
<input type="checkbox" v-model="showShortcutBvc" class="sr-only" />
|
||||||
<!-- line -->
|
<!-- line -->
|
||||||
<div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
|
<div class="block bg-slate-500 w-14 h-8 rounded-full" />
|
||||||
<!-- dot -->
|
<!-- dot -->
|
||||||
<div
|
<div
|
||||||
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
|
||||||
></div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -665,6 +687,7 @@ export default class AccountViewView extends Vue {
|
|||||||
showB64Copy = false;
|
showB64Copy = false;
|
||||||
showPubCopy = false;
|
showPubCopy = false;
|
||||||
showAdvanced = false;
|
showAdvanced = false;
|
||||||
|
hideRegisterPromptOnNewContact = false;
|
||||||
showShortcutBvc = false;
|
showShortcutBvc = false;
|
||||||
subscription: PushSubscription | null = null;
|
subscription: PushSubscription | null = null;
|
||||||
warnIfProdServer = false;
|
warnIfProdServer = false;
|
||||||
@@ -726,6 +749,8 @@ export default class AccountViewView extends Vue {
|
|||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
this.profileImageUrl = settings?.profileImageUrl as string;
|
this.profileImageUrl = settings?.profileImageUrl as string;
|
||||||
this.showContactGives = !!settings?.showContactGivesInline;
|
this.showContactGives = !!settings?.showContactGivesInline;
|
||||||
|
this.hideRegisterPromptOnNewContact =
|
||||||
|
!!settings?.hideRegisterPromptOnNewContact;
|
||||||
this.showShortcutBvc = !!settings?.showShortcutBvc;
|
this.showShortcutBvc = !!settings?.showShortcutBvc;
|
||||||
this.warnIfProdServer = !!settings?.warnIfProdServer;
|
this.warnIfProdServer = !!settings?.warnIfProdServer;
|
||||||
this.warnIfTestServer = !!settings?.warnIfTestServer;
|
this.warnIfTestServer = !!settings?.warnIfTestServer;
|
||||||
@@ -951,6 +976,28 @@ export default class AccountViewView extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async toggleHideRegisterPromptOnNewContact() {
|
||||||
|
const newSetting = !this.hideRegisterPromptOnNewContact;
|
||||||
|
try {
|
||||||
|
await db.open();
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
hideRegisterPromptOnNewContact: newSetting,
|
||||||
|
});
|
||||||
|
this.hideRegisterPromptOnNewContact = newSetting;
|
||||||
|
} catch (err) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error Updating Setting",
|
||||||
|
text: "The setting may not have saved. Try again, maybe after restarting the app.",
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
console.error("Telling user to try again because:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async updateShowShortcutBvc(newSetting: boolean) {
|
public async updateShowShortcutBvc(newSetting: boolean) {
|
||||||
try {
|
try {
|
||||||
await db.open();
|
await db.open();
|
||||||
|
|||||||
@@ -71,6 +71,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { AxiosError } from "axios";
|
||||||
import * as didJwt from "did-jwt";
|
import * as didJwt from "did-jwt";
|
||||||
import { sha256 } from "ethereum-cryptography/sha256.js";
|
import { sha256 } from "ethereum-cryptography/sha256.js";
|
||||||
import QRCodeVue3 from "qr-code-generator-vue3";
|
import QRCodeVue3 from "qr-code-generator-vue3";
|
||||||
@@ -95,6 +96,7 @@ import {
|
|||||||
CONTACT_URL_PREFIX,
|
CONTACT_URL_PREFIX,
|
||||||
ENDORSER_JWT_URL_LOCATION,
|
ENDORSER_JWT_URL_LOCATION,
|
||||||
isDid,
|
isDid,
|
||||||
|
register,
|
||||||
setVisibilityUtil,
|
setVisibilityUtil,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
|
|
||||||
@@ -113,6 +115,8 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
activeDid = "";
|
activeDid = "";
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
givenName = "";
|
givenName = "";
|
||||||
|
hideRegisterPromptOnNewContact = false;
|
||||||
|
isRegistered = false;
|
||||||
qrValue = "";
|
qrValue = "";
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
@@ -121,6 +125,9 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
this.activeDid = (settings?.activeDid as string) || "";
|
this.activeDid = (settings?.activeDid as string) || "";
|
||||||
this.apiServer = (settings?.apiServer as string) || "";
|
this.apiServer = (settings?.apiServer as string) || "";
|
||||||
this.givenName = (settings?.firstName as string) || "";
|
this.givenName = (settings?.firstName as string) || "";
|
||||||
|
this.hideRegisterPromptOnNewContact =
|
||||||
|
!!settings?.hideRegisterPromptOnNewContact;
|
||||||
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
|
||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
@@ -247,6 +254,7 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
let addedMessage;
|
let addedMessage;
|
||||||
if (this.activeDid) {
|
if (this.activeDid) {
|
||||||
await this.setVisibility(newContact, true);
|
await this.setVisibility(newContact, true);
|
||||||
|
newContact.seesMe = true; // didn't work inside setVisibility
|
||||||
addedMessage =
|
addedMessage =
|
||||||
"They were added, and your activity is visible to them.";
|
"They were added, and your activity is visible to them.";
|
||||||
} else {
|
} else {
|
||||||
@@ -261,6 +269,42 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
},
|
},
|
||||||
3000,
|
3000,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.isRegistered) {
|
||||||
|
if (!this.hideRegisterPromptOnNewContact && !newContact.registered) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "modal",
|
||||||
|
type: "confirm",
|
||||||
|
title: "Register",
|
||||||
|
text: "Do you want to register them?",
|
||||||
|
onCancel: async (stopAsking: boolean) => {
|
||||||
|
if (stopAsking) {
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
hideRegisterPromptOnNewContact: stopAsking,
|
||||||
|
});
|
||||||
|
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNo: async (stopAsking: boolean) => {
|
||||||
|
if (stopAsking) {
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
hideRegisterPromptOnNewContact: stopAsking,
|
||||||
|
});
|
||||||
|
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onYes: async () => {
|
||||||
|
await this.register(newContact);
|
||||||
|
},
|
||||||
|
promptToStopAsking: true,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error saving contact info:", e);
|
console.error("Error saving contact info:", e);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -302,6 +346,79 @@ export default class ContactQRScanShow extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async register(contact: Contact) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "toast",
|
||||||
|
text: "",
|
||||||
|
title: "Registration submitted...",
|
||||||
|
},
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const regResult = await register(
|
||||||
|
this.activeDid,
|
||||||
|
this.apiServer,
|
||||||
|
this.axios,
|
||||||
|
contact,
|
||||||
|
);
|
||||||
|
if (regResult.success) {
|
||||||
|
contact.registered = true;
|
||||||
|
db.contacts.update(contact.did, { registered: true });
|
||||||
|
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Registration Success",
|
||||||
|
text:
|
||||||
|
(contact.name || "That unnamed person") + " has been registered.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Registration Error",
|
||||||
|
text:
|
||||||
|
(regResult.error as string) ||
|
||||||
|
"Something went wrong during registration.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error when registering:", error);
|
||||||
|
let userMessage = "There was an error. See logs for more info.";
|
||||||
|
const serverError = error as AxiosError;
|
||||||
|
if (serverError) {
|
||||||
|
if (serverError.response?.data?.error?.message) {
|
||||||
|
userMessage = serverError.response.data.error.message;
|
||||||
|
} else if (serverError.message) {
|
||||||
|
userMessage = serverError.message; // Info for the user
|
||||||
|
} else {
|
||||||
|
userMessage = JSON.stringify(serverError.toJSON());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userMessage = error as string;
|
||||||
|
}
|
||||||
|
// Now set that error for the user to see.
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Registration Error",
|
||||||
|
text: userMessage,
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onScanError(error: any) {
|
onScanError(error: any) {
|
||||||
console.error("Scan was invalid:", error);
|
console.error("Scan was invalid:", error);
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
? "Confirmed Amounts"
|
? "Confirmed Amounts"
|
||||||
: "Unconfirmed Amounts"
|
: "Unconfirmed Amounts"
|
||||||
}}
|
}}
|
||||||
<fa icon="rotate" class="fa-fw" />
|
<fa icon="left-right" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -301,30 +301,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AxiosError } from "axios";
|
import {Axios, AxiosError} from "axios";
|
||||||
import { IndexableType } from "dexie";
|
import { IndexableType } from "dexie";
|
||||||
import * as didJwt from "did-jwt";
|
|
||||||
import * as R from "ramda";
|
import * as R from "ramda";
|
||||||
import { IIdentifier } from "@veramo/core";
|
import { IIdentifier } from "@veramo/core";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
import { AppString, NotificationIface } from "@/constants/app";
|
import { AppString, NotificationIface } from "@/constants/app";
|
||||||
import { accountsDB, db } from "@/db/index";
|
import {accountsDB, db, NonsensitiveDexie} from "@/db/index";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
|
||||||
import {
|
import { accessToken, getContactPayloadFromJwtUrl } from "@/libs/crypto";
|
||||||
accessToken,
|
|
||||||
getContactPayloadFromJwtUrl,
|
|
||||||
SimpleSigner,
|
|
||||||
} from "@/libs/crypto";
|
|
||||||
import {
|
import {
|
||||||
CONTACT_CSV_HEADER,
|
CONTACT_CSV_HEADER,
|
||||||
CONTACT_URL_PREFIX,
|
CONTACT_URL_PREFIX,
|
||||||
GiverReceiverInputInfo,
|
GiverReceiverInputInfo,
|
||||||
GiveSummaryRecord,
|
GiveSummaryRecord,
|
||||||
isDid,
|
isDid,
|
||||||
RegisterVerifiableCredential,
|
register,
|
||||||
SERVICE_ID,
|
|
||||||
setVisibilityUtil,
|
setVisibilityUtil,
|
||||||
} from "@/libs/endorserServer";
|
} from "@/libs/endorserServer";
|
||||||
import * as libsUtil from "@/libs/util";
|
import * as libsUtil from "@/libs/util";
|
||||||
@@ -335,6 +330,7 @@ import OfferDialog from "@/components/OfferDialog.vue";
|
|||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
|
|
||||||
import { Buffer } from "buffer/";
|
import { Buffer } from "buffer/";
|
||||||
|
import {getIdentity} from "@/libs/util";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { GiftedDialog, EntityIcon, OfferDialog, QuickNav },
|
components: { GiftedDialog, EntityIcon, OfferDialog, QuickNav },
|
||||||
@@ -360,6 +356,7 @@ export default class ContactsView extends Vue {
|
|||||||
givenToMeConfirmed: Record<string, number> = {};
|
givenToMeConfirmed: Record<string, number> = {};
|
||||||
// { "did:...": amount } entry for each contact
|
// { "did:...": amount } entry for each contact
|
||||||
givenToMeUnconfirmed: Record<string, number> = {};
|
givenToMeUnconfirmed: Record<string, number> = {};
|
||||||
|
hideRegisterPromptOnNewContact = false;
|
||||||
isRegistered = false;
|
isRegistered = false;
|
||||||
showDidCopy = false;
|
showDidCopy = false;
|
||||||
showGiveNumbers = false;
|
showGiveNumbers = false;
|
||||||
@@ -378,6 +375,9 @@ export default class ContactsView extends Vue {
|
|||||||
this.isRegistered = !!settings?.isRegistered;
|
this.isRegistered = !!settings?.isRegistered;
|
||||||
|
|
||||||
this.showGiveNumbers = !!settings?.showContactGivesInline;
|
this.showGiveNumbers = !!settings?.showContactGivesInline;
|
||||||
|
this.hideRegisterPromptOnNewContact =
|
||||||
|
!!settings?.hideRegisterPromptOnNewContact;
|
||||||
|
|
||||||
if (this.showGiveNumbers) {
|
if (this.showGiveNumbers) {
|
||||||
this.loadGives();
|
this.loadGives();
|
||||||
}
|
}
|
||||||
@@ -681,6 +681,7 @@ export default class ContactsView extends Vue {
|
|||||||
nextPubKeyHashB64: payload.own.nextPublicEncKeyHash,
|
nextPubKeyHashB64: payload.own.nextPublicEncKeyHash,
|
||||||
profileImageUrl: payload.own.profileImageUrl,
|
profileImageUrl: payload.own.profileImageUrl,
|
||||||
publicKeyBase64: payload.own.publicEncKey,
|
publicKeyBase64: payload.own.publicEncKey,
|
||||||
|
isRegistered: payload.own.isRegistered,
|
||||||
} as Contact);
|
} as Contact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,6 +706,7 @@ export default class ContactsView extends Vue {
|
|||||||
let addedMessage;
|
let addedMessage;
|
||||||
if (this.activeDid) {
|
if (this.activeDid) {
|
||||||
this.setVisibility(newContact, true, false);
|
this.setVisibility(newContact, true, false);
|
||||||
|
newContact.seesMe = true; // didn't work inside setVisibility
|
||||||
addedMessage =
|
addedMessage =
|
||||||
"They were added, and your activity is visible to them.";
|
"They were added, and your activity is visible to them.";
|
||||||
} else {
|
} else {
|
||||||
@@ -712,15 +714,39 @@ export default class ContactsView extends Vue {
|
|||||||
}
|
}
|
||||||
this.contactInput = "";
|
this.contactInput = "";
|
||||||
if (this.isRegistered) {
|
if (this.isRegistered) {
|
||||||
this.$notify(
|
if (!this.hideRegisterPromptOnNewContact && !newContact.registered) {
|
||||||
{
|
setTimeout(() => {
|
||||||
group: "alert",
|
this.$notify(
|
||||||
type: "info",
|
{
|
||||||
title: "New User?",
|
group: "modal",
|
||||||
text: "If they are a new user, be sure to register to onboard them.",
|
type: "confirm",
|
||||||
},
|
title: "Register",
|
||||||
-1,
|
text: "Do you want to register them?",
|
||||||
);
|
onCancel: async (stopAsking: boolean) => {
|
||||||
|
if (stopAsking) {
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
hideRegisterPromptOnNewContact: stopAsking,
|
||||||
|
});
|
||||||
|
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNo: async (stopAsking: boolean) => {
|
||||||
|
if (stopAsking) {
|
||||||
|
db.settings.update(MASTER_SETTINGS_KEY, {
|
||||||
|
hideRegisterPromptOnNewContact: stopAsking,
|
||||||
|
});
|
||||||
|
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onYes: async () => {
|
||||||
|
await this.register(newContact);
|
||||||
|
},
|
||||||
|
promptToStopAsking: true,
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
@@ -809,99 +835,65 @@ export default class ContactsView extends Vue {
|
|||||||
1000,
|
1000,
|
||||||
);
|
);
|
||||||
|
|
||||||
const identity = await this.getIdentity(this.activeDid);
|
try {
|
||||||
|
const regResult = await register(
|
||||||
|
this.activeDid,
|
||||||
|
this.apiServer,
|
||||||
|
this.axios,
|
||||||
|
contact,
|
||||||
|
);
|
||||||
|
if (regResult.success) {
|
||||||
|
contact.registered = true;
|
||||||
|
db.contacts.update(contact.did, { registered: true });
|
||||||
|
|
||||||
const vcClaim: RegisterVerifiableCredential = {
|
this.$notify(
|
||||||
"@context": "https://schema.org",
|
{
|
||||||
"@type": "RegisterAction",
|
group: "alert",
|
||||||
agent: { identifier: identity.did },
|
type: "success",
|
||||||
object: SERVICE_ID,
|
title: "Registration Success",
|
||||||
participant: { identifier: contact.did },
|
text:
|
||||||
};
|
(contact.name || "That unnamed person") + " has been registered.",
|
||||||
// Make a payload for the claim
|
},
|
||||||
const vcPayload = {
|
5000,
|
||||||
vc: {
|
);
|
||||||
"@context": ["https://www.w3.org/2018/credentials/v1"],
|
} else {
|
||||||
type: ["VerifiableCredential"],
|
|
||||||
credentialSubject: vcClaim,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// Create a signature using private key of identity
|
|
||||||
if (identity.keys[0].privateKeyHex !== null) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
const privateKeyHex: string = identity.keys[0].privateKeyHex!;
|
|
||||||
const signer = await SimpleSigner(privateKeyHex);
|
|
||||||
const alg = undefined;
|
|
||||||
// Create a JWT for the request
|
|
||||||
const vcJwt: string = await didJwt.createJWT(vcPayload, {
|
|
||||||
alg: alg,
|
|
||||||
issuer: identity.did,
|
|
||||||
signer: signer,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make the xhr request payload
|
|
||||||
const payload = JSON.stringify({ jwtEncoded: vcJwt });
|
|
||||||
const url = this.apiServer + "/api/v2/claim";
|
|
||||||
const headers = await this.getHeaders(identity);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const resp = await this.axios.post(url, payload, { headers });
|
|
||||||
if (resp.data?.success?.embeddedRecordError) {
|
|
||||||
let message = "There was some problem with the registration.";
|
|
||||||
if (typeof resp.data.success.embeddedRecordError == "string") {
|
|
||||||
message += " " + resp.data.success.embeddedRecordError;
|
|
||||||
}
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Registration Still Unknown",
|
|
||||||
text: message,
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
|
||||||
} else if (resp.data?.success?.handleId) {
|
|
||||||
contact.registered = true;
|
|
||||||
db.contacts.update(contact.did, { registered: true });
|
|
||||||
|
|
||||||
this.$notify(
|
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "success",
|
|
||||||
title: "Registration Success",
|
|
||||||
text:
|
|
||||||
(contact.name || "That unnamed person") +
|
|
||||||
" has been registered.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error when registering:", error);
|
|
||||||
let userMessage = "There was an error. See logs for more info.";
|
|
||||||
const serverError = error as AxiosError;
|
|
||||||
if (serverError) {
|
|
||||||
if (serverError.response?.data?.error?.message) {
|
|
||||||
userMessage = serverError.response.data.error.message;
|
|
||||||
} else if (serverError.message) {
|
|
||||||
userMessage = serverError.message; // Info for the user
|
|
||||||
} else {
|
|
||||||
userMessage = JSON.stringify(serverError.toJSON());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
userMessage = error as string;
|
|
||||||
}
|
|
||||||
// Now set that error for the user to see.
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Registration Error",
|
title: "Registration Error",
|
||||||
text: userMessage,
|
text:
|
||||||
|
(regResult.error as string) ||
|
||||||
|
"Something went wrong during registration.",
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error when registering:", error);
|
||||||
|
let userMessage = "There was an error. See logs for more info.";
|
||||||
|
const serverError = error as AxiosError;
|
||||||
|
if (serverError) {
|
||||||
|
if (serverError.response?.data?.error?.message) {
|
||||||
|
userMessage = serverError.response.data.error.message;
|
||||||
|
} else if (serverError.message) {
|
||||||
|
userMessage = serverError.message; // Info for the user
|
||||||
|
} else {
|
||||||
|
userMessage = JSON.stringify(serverError.toJSON());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userMessage = error as string;
|
||||||
|
}
|
||||||
|
// Now set that error for the user to see.
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Registration Error",
|
||||||
|
text: userMessage,
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,6 +972,13 @@ export default class ContactsView extends Vue {
|
|||||||
if (resp.status === 200) {
|
if (resp.status === 200) {
|
||||||
const visibility = resp.data;
|
const visibility = resp.data;
|
||||||
contact.seesMe = visibility;
|
contact.seesMe = visibility;
|
||||||
|
console.log(
|
||||||
|
"Visibility checked:",
|
||||||
|
visibility,
|
||||||
|
contact.did,
|
||||||
|
contact.name,
|
||||||
|
); // eslint-disable-line no-console
|
||||||
|
console.log(this.contacts); // eslint-disable-line no-console
|
||||||
db.contacts.update(contact.did, { seesMe: visibility });
|
db.contacts.update(contact.did, { seesMe: visibility });
|
||||||
|
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -1064,7 +1063,7 @@ export default class ContactsView extends Vue {
|
|||||||
this.showGiftedDialog(giverDid, recipientDid);
|
this.showGiftedDialog(giverDid, recipientDid);
|
||||||
},
|
},
|
||||||
onYes: async () => {
|
onYes: async () => {
|
||||||
this.$router.push({
|
(this.$router as Router).push({
|
||||||
name: "contact-amounts",
|
name: "contact-amounts",
|
||||||
query: { contactDid: giverDid },
|
query: { contactDid: giverDid },
|
||||||
});
|
});
|
||||||
@@ -1159,6 +1158,18 @@ export default class ContactsView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.showGiveNumbers = newShowValue;
|
this.showGiveNumbers = newShowValue;
|
||||||
|
if (
|
||||||
|
newShowValue &&
|
||||||
|
Object.keys(this.givenByMeDescriptions).length === 0 &&
|
||||||
|
Object.keys(this.givenByMeConfirmed).length === 0 &&
|
||||||
|
Object.keys(this.givenByMeUnconfirmed).length === 0 &&
|
||||||
|
Object.keys(this.givenToMeDescriptions).length === 0 &&
|
||||||
|
Object.keys(this.givenToMeConfirmed).length === 0 &&
|
||||||
|
Object.keys(this.givenToMeUnconfirmed).length === 0
|
||||||
|
) {
|
||||||
|
// assume we should load it all
|
||||||
|
this.loadGives();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public toggleShowGiveTotals() {
|
public toggleShowGiveTotals() {
|
||||||
if (this.showGiveTotals) {
|
if (this.showGiveTotals) {
|
||||||
|
|||||||
@@ -121,7 +121,7 @@
|
|||||||
v-if="!isLoading && claims.length === 0"
|
v-if="!isLoading && claims.length === 0"
|
||||||
class="flex justify-center mt-4"
|
class="flex justify-center mt-4"
|
||||||
>
|
>
|
||||||
<span>No Claims Visible to You Involve Them</span>
|
<span>They Are in No Claims Visible to You</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user