forked from trent_larson/crowd-funder-for-time-pwa
add invite-one-accept screen dedicated to accepting invitations
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
|
||||||
# I tried and failed to set things here with vue-cli-service but
|
# I tried and failed to set things here with vue-cli-service but
|
||||||
# things may be more reliable with vite so let's try again.
|
# things may be more reliable with vite so let's try again.
|
||||||
|
|
||||||
|
VITE_APP_SERVER=http://localhost:8080
|
||||||
|
|||||||
@@ -58,9 +58,12 @@ npm run test-all
|
|||||||
* Run the correct build:
|
* Run the correct build:
|
||||||
|
|
||||||
* Staging
|
* Staging
|
||||||
|
|
||||||
|
(Let's replace this with a .env.development or .env.staging file.)
|
||||||
|
|
||||||
|
The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.
|
||||||
|
|
||||||
```
|
```
|
||||||
# (Let's replace this with a .env.development or .env.staging file.)
|
|
||||||
# The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.
|
|
||||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build
|
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
23
src/App.vue
23
src/App.vue
@@ -309,28 +309,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
|
|
||||||
>
|
|
||||||
<div class="w-full px-6 py-6 text-slate-900 text-center">
|
|
||||||
<p class="text-lg mb-4">
|
|
||||||
Something has gone very wrong. We'd appreciate if you'd
|
|
||||||
contact us and let us know how you got here. Thank you!
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
@click="close(notification.id)"
|
|
||||||
class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Notification>
|
</Notification>
|
||||||
</div>
|
</div>
|
||||||
@@ -398,6 +376,7 @@ export default class App extends Vue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clone in order to get only the properties and allow stringify to work
|
||||||
const serverSubscription = {
|
const serverSubscription = {
|
||||||
...subscription,
|
...subscription,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1070,6 +1070,7 @@ export async function generateEndorserJwtForAccount(
|
|||||||
contactInfo.own.profileImageUrl = profileImageUrl;
|
contactInfo.own.profileImageUrl = profileImageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the next key -- but note that it makes the QR more detailed
|
||||||
if (includeNextKeyIfDerived && account?.mnemonic && account?.derivationPath) {
|
if (includeNextKeyIfDerived && account?.mnemonic && account?.derivationPath) {
|
||||||
const newDerivPath = nextDerivationPath(account.derivationPath as string);
|
const newDerivPath = nextDerivationPath(account.derivationPath as string);
|
||||||
const nextPublicHex = deriveAddress(
|
const nextPublicHex = deriveAddress(
|
||||||
@@ -1082,6 +1083,7 @@ export async function generateEndorserJwtForAccount(
|
|||||||
Buffer.from(nextPublicEncKeyHash).toString("base64");
|
Buffer.from(nextPublicEncKeyHash).toString("base64");
|
||||||
contactInfo.own.nextPublicEncKeyHash = nextPublicEncKeyHashBase64;
|
contactInfo.own.nextPublicEncKeyHash = nextPublicEncKeyHashBase64;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vcJwt = await createEndorserJwtForDid(account.did, contactInfo);
|
const vcJwt = await createEndorserJwtForDid(account.did, contactInfo);
|
||||||
|
|
||||||
const viewPrefix = CONTACT_URL_PREFIX + ENDORSER_JWT_URL_LOCATION;
|
const viewPrefix = CONTACT_URL_PREFIX + ENDORSER_JWT_URL_LOCATION;
|
||||||
|
|||||||
@@ -172,11 +172,14 @@ function setupGlobalErrorHandler(app: VueApp) {
|
|||||||
info: string,
|
info: string,
|
||||||
) => {
|
) => {
|
||||||
console.error(
|
console.error(
|
||||||
"Ouch! Global Error Handler. Info:",
|
"Ouch! Global Error Handler.",
|
||||||
info,
|
|
||||||
"Error:",
|
"Error:",
|
||||||
err,
|
err,
|
||||||
"Instance:",
|
"- Error toString:",
|
||||||
|
err.toString(),
|
||||||
|
"- Info:",
|
||||||
|
info,
|
||||||
|
"- Instance:",
|
||||||
instance,
|
instance,
|
||||||
);
|
);
|
||||||
// Want to show a nice notiwind notification but can't figure out how.
|
// Want to show a nice notiwind notification but can't figure out how.
|
||||||
|
|||||||
@@ -144,6 +144,11 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: "invite-one",
|
name: "invite-one",
|
||||||
component: () => import("../views/InviteOneView.vue"),
|
component: () => import("../views/InviteOneView.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/invite-one-accept/:jwt?",
|
||||||
|
name: "InviteOneAcceptView",
|
||||||
|
component: () => import("@/views/InviteOneAcceptView.vue"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/new-activity",
|
path: "/new-activity",
|
||||||
name: "new-activity",
|
name: "new-activity",
|
||||||
|
|||||||
@@ -243,7 +243,7 @@
|
|||||||
{{ notifyingNewActivityTime.replace(" ", " ") }}
|
{{ notifyingNewActivityTime.replace(" ", " ") }}
|
||||||
</div>
|
</div>
|
||||||
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
|
<router-link class="pl-4 text-sm text-blue-500" to="/help-notifications">
|
||||||
Troubleshoot your notification setup.
|
Troubleshoot your notifications.
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<PushNotificationPermission ref="pushNotificationPermission" />
|
<PushNotificationPermission ref="pushNotificationPermission" />
|
||||||
@@ -918,24 +918,6 @@ export default class AccountViewView extends Vue {
|
|||||||
// Initialize component state with values from the database or defaults
|
// Initialize component state with values from the database or defaults
|
||||||
await this.initializeState();
|
await this.initializeState();
|
||||||
await this.processIdentity();
|
await this.processIdentity();
|
||||||
|
|
||||||
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Beware! I've seen where this "ready" never resolves.
|
|
||||||
*/
|
|
||||||
const registration = await navigator.serviceWorker?.ready;
|
|
||||||
this.subscription = await registration.pushManager.getSubscription();
|
|
||||||
if (!this.subscription) {
|
|
||||||
if (this.notifyingNewActivity || this.notifyingReminder) {
|
|
||||||
// the app thought there was a subscription but there isn't, so fix the settings
|
|
||||||
this.turnOffNotifyingFlags();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// console.log("Got to the end of 'mounted' call in AccountViewView.");
|
|
||||||
/**
|
|
||||||
* Beware! I've seen where we never get to this point because "ready" never resolves.
|
|
||||||
*/
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// this can happen when running automated tests in dev mode because notifications don't work
|
// this can happen when running automated tests in dev mode because notifications don't work
|
||||||
console.error(
|
console.error(
|
||||||
@@ -957,6 +939,35 @@ export default class AccountViewView extends Vue {
|
|||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/**
|
||||||
|
* Beware! I've seen where this "ready" never resolves.
|
||||||
|
*/
|
||||||
|
const registration = await navigator.serviceWorker?.ready;
|
||||||
|
this.subscription = await registration.pushManager.getSubscription();
|
||||||
|
if (!this.subscription) {
|
||||||
|
if (this.notifyingNewActivity || this.notifyingReminder) {
|
||||||
|
// the app thought there was a subscription but there isn't, so fix the settings
|
||||||
|
this.turnOffNotifyingFlags();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.log("Got to the end of 'mounted' call in AccountViewView.");
|
||||||
|
/**
|
||||||
|
* Beware! I've seen where we never get to this point because "ready" never resolves.
|
||||||
|
*/
|
||||||
|
} catch (error) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "warning",
|
||||||
|
title: "Cannot Set Notifications",
|
||||||
|
text: "This browser does not support notifications. Try Chrome or Safari, or other suggestions on the 'Troubleshoot your notifications' page.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ export default class ContactsView extends Vue {
|
|||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "warning",
|
type: "danger",
|
||||||
title: "Blank Invite",
|
title: "Blank Invite",
|
||||||
text: "The invite was not included, which can happen when your iOS device cuts off the link. Try pasting the full link into a browser.",
|
text: "The invite was not included, which can happen when your iOS device cuts off the link. Try pasting the full link into a browser.",
|
||||||
},
|
},
|
||||||
@@ -474,29 +474,36 @@ export default class ContactsView extends Vue {
|
|||||||
3000,
|
3000,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// wait for a second before continuing so they see the registration message
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
// now add the inviter as a contact
|
// now add the inviter as a contact
|
||||||
|
// (similar code is in InviteOneAcceptView.vue)
|
||||||
const payload: JWTPayload =
|
const payload: JWTPayload =
|
||||||
decodeEndorserJwt(importedInviteJwt).payload;
|
decodeEndorserJwt(importedInviteJwt).payload;
|
||||||
const registration = payload as VerifiableCredential;
|
const registration = payload as VerifiableCredential;
|
||||||
(this.$refs.contactNameDialog as ContactNameDialog).open(
|
(this.$refs.contactNameDialog as ContactNameDialog).open(
|
||||||
"Who Invited You?",
|
"Who Invited You?",
|
||||||
"",
|
"",
|
||||||
(name) => {
|
async (name) => {
|
||||||
// not doing await on purpose, so that they always see the onboarding
|
await this.addContact({
|
||||||
this.addContact({
|
|
||||||
did: registration.vc.credentialSubject.agent.identifier,
|
did: registration.vc.credentialSubject.agent.identifier,
|
||||||
name: name,
|
name: name,
|
||||||
registered: true,
|
registered: true,
|
||||||
});
|
});
|
||||||
|
// wait for a second before continuing so they see the user-added message
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
this.showOnboardingInfo();
|
this.showOnboardingInfo();
|
||||||
},
|
},
|
||||||
() => {
|
async () => {
|
||||||
// not doing await on purpose, so that they always see the onboarding
|
// on cancel, will still add the contact
|
||||||
this.addContact({
|
await this.addContact({
|
||||||
did: registration.vc.credentialSubject.agent.identifier,
|
did: registration.vc.credentialSubject.agent.identifier,
|
||||||
name: "(person who invited you)",
|
name: "(person who invited you)",
|
||||||
registered: true,
|
registered: true,
|
||||||
});
|
});
|
||||||
|
// wait for a second before continuing so they see the user-added message
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
this.showOnboardingInfo();
|
this.showOnboardingInfo();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -73,7 +73,8 @@
|
|||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div v-if="isCreatingIdentifier">
|
<div v-if="isCreatingIdentifier">
|
||||||
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
<p class="text-slate-500 text-center italic mt-4 mb-4">
|
||||||
<fa icon="spinner" class="fa-spin-pulse" /> Loading…
|
<fa icon="spinner" class="fa-spin-pulse" />
|
||||||
|
Loading…
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
138
src/views/InviteOneAcceptView.vue
Normal file
138
src/views/InviteOneAcceptView.vue
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<template>
|
||||||
|
<QuickNav selected="Invite" />
|
||||||
|
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||||
|
<div v-if="acceptInput" class="text-center mt-4">
|
||||||
|
<p>That invitation did not work.</p>
|
||||||
|
<p class="mt-2">
|
||||||
|
Go back to your invite message and copy the entire text, then paste it
|
||||||
|
here.
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
v-model="inputJwt"
|
||||||
|
type="text"
|
||||||
|
placeholder="Paste invitation..."
|
||||||
|
class="mt-4 border-2 border-gray-300 p-2 rounded"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
@click="() => processInvite(inputJwt, true)"
|
||||||
|
class="ml-2 p-2 bg-blue-500 text-white rounded"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="checkingInvite"
|
||||||
|
class="text-lg text-center font-light relative px-7"
|
||||||
|
>
|
||||||
|
<fa icon="spinner" class="fa-spin-pulse" />
|
||||||
|
Loading…
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
|
import { NotificationIface } from "@/constants/app";
|
||||||
|
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||||
|
import { decodeEndorserJwt } from "@/libs/crypto/vc";
|
||||||
|
import { generateSaveAndActivateIdentity } from "@/libs/util";
|
||||||
|
|
||||||
|
@Component({ components: { QuickNav } })
|
||||||
|
export default class InviteOneAcceptView extends Vue {
|
||||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
acceptInput: boolean = false;
|
||||||
|
activeDid: string = "";
|
||||||
|
apiServer: string = "";
|
||||||
|
checkingInvite: boolean = true;
|
||||||
|
inputJwt: string = "";
|
||||||
|
|
||||||
|
async mounted() {
|
||||||
|
this.checkingInvite = true;
|
||||||
|
await db.open();
|
||||||
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
|
this.activeDid = settings.activeDid || "";
|
||||||
|
this.apiServer = settings.apiServer || "";
|
||||||
|
|
||||||
|
if (!this.activeDid) {
|
||||||
|
this.activeDid = await generateSaveAndActivateIdentity();
|
||||||
|
}
|
||||||
|
|
||||||
|
const jwt = window.location.pathname.substring(
|
||||||
|
"/invite-one-accept/".length,
|
||||||
|
);
|
||||||
|
await this.processInvite(jwt, false);
|
||||||
|
|
||||||
|
this.checkingInvite = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process the invite JWT and/or text message containing the URL with the JWT
|
||||||
|
async processInvite(jwtInput: string, notifyOnFailure: boolean) {
|
||||||
|
this.checkingInvite = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let jwt: string = jwtInput ?? "";
|
||||||
|
|
||||||
|
// parse the string: extract the URL or JWT if surrounded by spaces
|
||||||
|
// and then extract the JWT from the URL
|
||||||
|
const urlMatch = jwtInput.match(/(https?:\/\/[^\s]+)/);
|
||||||
|
if (urlMatch && urlMatch[1]) {
|
||||||
|
// extract the JWT from the URL, meaning any character except "?"
|
||||||
|
const internalMatch = urlMatch[1].match(/\/invite-one-accept\/([^?]+)/);
|
||||||
|
if (internalMatch && internalMatch[1]) {
|
||||||
|
jwt = internalMatch[1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// extract the JWT (which starts with "ey") if it is surrounded by other input
|
||||||
|
const spaceMatch = jwtInput.match(/(ey[\w.-]+)/);
|
||||||
|
if (spaceMatch && spaceMatch[1]) {
|
||||||
|
jwt = spaceMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jwt) {
|
||||||
|
if (notifyOnFailure) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Missing invite",
|
||||||
|
text: "There was no invite. Paste the entire text that has the link.",
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.acceptInput = true;
|
||||||
|
} else {
|
||||||
|
//const payload: JWTPayload =
|
||||||
|
decodeEndorserJwt(jwt);
|
||||||
|
|
||||||
|
// That's good enough for an initial check.
|
||||||
|
// Send them to the contacts page to finish, with inviteJwt in the query string.
|
||||||
|
(this.$router as Router).push({
|
||||||
|
name: "contacts",
|
||||||
|
query: { inviteJwt: jwt },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error accepting invite:", error);
|
||||||
|
if (notifyOnFailure) {
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "danger",
|
||||||
|
title: "Error",
|
||||||
|
text: "There was an error processing that invite.",
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.acceptInput = true;
|
||||||
|
}
|
||||||
|
this.checkingInvite = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
{{ invite.notes }}
|
{{ invite.notes }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{{ invite.expiresAt.substring(0, 10) }}
|
{{ invite.redeemedAt ? "" : invite.expiresAt.substring(0, 10) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{{ invite.redeemedAt?.substring(0, 10) }}
|
{{ invite.redeemedAt?.substring(0, 10) }}
|
||||||
@@ -137,8 +137,9 @@ import ContactNameDialog from "@/components/ContactNameDialog.vue";
|
|||||||
import QuickNav from "@/components/QuickNav.vue";
|
import QuickNav from "@/components/QuickNav.vue";
|
||||||
import TopMessage from "@/components/TopMessage.vue";
|
import TopMessage from "@/components/TopMessage.vue";
|
||||||
import InviteDialog from "@/components/InviteDialog.vue";
|
import InviteDialog from "@/components/InviteDialog.vue";
|
||||||
import { APP_SERVER, NotificationIface } from "@/constants/app";
|
import { APP_SERVER, AppString, NotificationIface } from "@/constants/app";
|
||||||
import { db, retrieveSettingsForActiveAccount } from "@/db";
|
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||||
|
import { Contact } from "@/db/tables/contacts";
|
||||||
import { createInviteJwt, getHeaders } from "@/libs/endorserServer";
|
import { createInviteJwt, getHeaders } from "@/libs/endorserServer";
|
||||||
|
|
||||||
interface Invite {
|
interface Invite {
|
||||||
@@ -159,7 +160,7 @@ export default class InviteOneView extends Vue {
|
|||||||
invites: Invite[] = [];
|
invites: Invite[] = [];
|
||||||
activeDid: string = "";
|
activeDid: string = "";
|
||||||
apiServer: string = "";
|
apiServer: string = "";
|
||||||
contactsRedeemed = {};
|
contactsRedeemed: { [key: string]: Contact } = {};
|
||||||
isRegistered: boolean = false;
|
isRegistered: boolean = false;
|
||||||
showAppleWarning = false;
|
showAppleWarning = false;
|
||||||
|
|
||||||
@@ -178,12 +179,12 @@ export default class InviteOneView extends Vue {
|
|||||||
);
|
);
|
||||||
this.invites = response.data.data;
|
this.invites = response.data.data;
|
||||||
|
|
||||||
const baseContacts = await db.contacts.toArray();
|
const baseContacts: Contact[] = await db.contacts.toArray();
|
||||||
for (const invite of this.invites) {
|
for (const invite of this.invites) {
|
||||||
const contact = baseContacts.find(
|
const contact = baseContacts.find(
|
||||||
(contact) => contact.did === invite.redeemedBy,
|
(contact) => contact.did === invite.redeemedBy,
|
||||||
);
|
);
|
||||||
if (contact) {
|
if (contact && invite.redeemedBy) {
|
||||||
this.contactsRedeemed[invite.redeemedBy] = contact;
|
this.contactsRedeemed[invite.redeemedBy] = contact;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,14 +210,16 @@ export default class InviteOneView extends Vue {
|
|||||||
getTruncatedRedeemedBy(redeemedBy: string | null): string {
|
getTruncatedRedeemedBy(redeemedBy: string | null): string {
|
||||||
if (!redeemedBy) return "";
|
if (!redeemedBy) return "";
|
||||||
if (this.contactsRedeemed[redeemedBy]) {
|
if (this.contactsRedeemed[redeemedBy]) {
|
||||||
return this.contactsRedeemed[redeemedBy].name;
|
return (
|
||||||
|
this.contactsRedeemed[redeemedBy].name || AppString.NO_CONTACT_NAME
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (redeemedBy.length <= 19) return redeemedBy;
|
if (redeemedBy.length <= 19) return redeemedBy;
|
||||||
return `${redeemedBy.slice(0, 13)}...${redeemedBy.slice(-3)}`;
|
return `${redeemedBy.slice(0, 13)}...${redeemedBy.slice(-3)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
inviteLink(jwt: string): string {
|
inviteLink(jwt: string): string {
|
||||||
return APP_SERVER + "/contacts?inviteJwt=" + jwt;
|
return APP_SERVER + "/invite-one-accept/" + jwt;
|
||||||
}
|
}
|
||||||
|
|
||||||
copyInviteAndNotify(inviteId: string, jwt: string) {
|
copyInviteAndNotify(inviteId: string, jwt: string) {
|
||||||
@@ -251,7 +254,8 @@ export default class InviteOneView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
lookForErrorAndNotify(error, title: string, defaultMessage: string) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
lookForErrorAndNotify(error: any, title: string, defaultMessage: string) {
|
||||||
console.error(title, "-", error);
|
console.error(title, "-", error);
|
||||||
let message = defaultMessage;
|
let message = defaultMessage;
|
||||||
if (error.response && error.response.data && error.response.data.error) {
|
if (error.response && error.response.data && error.response.data.error) {
|
||||||
@@ -301,14 +305,15 @@ export default class InviteOneView extends Vue {
|
|||||||
{ inviteJwt: inviteJwt, notes: notes },
|
{ inviteJwt: inviteJwt, notes: notes },
|
||||||
{ headers },
|
{ headers },
|
||||||
);
|
);
|
||||||
this.invites.push({
|
const newInvite = {
|
||||||
inviteIdentifier: inviteIdentifier,
|
inviteIdentifier: inviteIdentifier,
|
||||||
expiresAt: expiresAt,
|
expiresAt: expiresAt,
|
||||||
jwt: inviteJwt,
|
jwt: inviteJwt,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
redeemedAt: null,
|
redeemedAt: null,
|
||||||
redeemedBy: null,
|
redeemedBy: null,
|
||||||
});
|
};
|
||||||
|
this.invites = [newInvite, ...this.invites];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.lookForErrorAndNotify(
|
this.lookForErrorAndNotify(
|
||||||
@@ -321,7 +326,7 @@ export default class InviteOneView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewContact(did) {
|
addNewContact(did: string) {
|
||||||
(this.$refs.contactNameDialog as ContactNameDialog).open(
|
(this.$refs.contactNameDialog as ContactNameDialog).open(
|
||||||
"To Whom Did You Send The Invite?",
|
"To Whom Did You Send The Invite?",
|
||||||
"Their name will be added to your contact list.",
|
"Their name will be added to your contact list.",
|
||||||
|
|||||||
Reference in New Issue
Block a user