show more redeemed info & action on the invites, refactor onboarding instructions
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
<!-- similar to ContactNameDialog -->
|
<!-- similar to UserNameDialog -->
|
||||||
<template>
|
<template>
|
||||||
<div v-if="visible" class="dialog-overlay">
|
<div v-if="visible" class="dialog-overlay">
|
||||||
<div class="dialog">
|
<div class="dialog">
|
||||||
<h1 class="text-xl font-bold text-center mb-4">Inviter's Name</h1>
|
<h1 class="text-xl font-bold text-center mb-4">Contact Name</h1>
|
||||||
Note that their name is only stored on this device.
|
Note that their name is only stored on this device.
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
import { Vue, Component } from "vue-facing-decorator";
|
import { Vue, Component } from "vue-facing-decorator";
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class UserNameDialog extends Vue {
|
export default class ContactNameDialog extends Vue {
|
||||||
callback: (name?: string) => void = () => {};
|
callback: (name?: string) => void = () => {};
|
||||||
newText = "";
|
newText = "";
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
<div class="dialog">
|
<div class="dialog">
|
||||||
<h1 class="text-xl font-bold text-center mb-4">Invitation & Notes</h1>
|
<h1 class="text-xl font-bold text-center mb-4">Invitation & Notes</h1>
|
||||||
|
|
||||||
These are optional notes for your use, to make comments for to recall
|
These are optional notes for your use; they are comments to help you
|
||||||
later when redeemed by someone. These notes are sent to the server. If you
|
recall who it is when they accept it. These notes are sent to the server.
|
||||||
want to store your own way, the invitation ID is: {{ inviteIdentifier }}
|
If you want to store your own way, the invitation ID is:
|
||||||
|
{{ inviteIdentifier }}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Notes"
|
placeholder="Notes"
|
||||||
|
|||||||
@@ -39,12 +39,12 @@
|
|||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="URL or DID, Name, Public Key, Next Public Key Hash"
|
placeholder="New URL or DID, Name, Public Key, Next Public Key Hash"
|
||||||
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2 h-10"
|
class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-2 h-10"
|
||||||
v-model="contactInput"
|
v-model="contactInput"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="px-4 rounded-r bg-slate-200 border border-l-0 border-slate-400"
|
class="px-4 rounded-r bg-green-200 border border-l-0 border-green-400"
|
||||||
@click="onClickNewContact()"
|
@click="onClickNewContact()"
|
||||||
>
|
>
|
||||||
<fa icon="plus" class="fa-fw" />
|
<fa icon="plus" class="fa-fw" />
|
||||||
|
|||||||
@@ -12,6 +12,24 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To invite someone, generate a link to send to them from this page:
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'invite-one' }"
|
||||||
|
class="bg-gradient-to-b from-green-400 to-green-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md"
|
||||||
|
>
|
||||||
|
<fa icon="envelope-open-text" class="fa-fw text-xl"
|
||||||
|
/></router-link>
|
||||||
|
</p>
|
||||||
|
<p>That's all it takes.</p>
|
||||||
|
<p>Watch that page to see when people accept their invite.</p>
|
||||||
|
<p>
|
||||||
|
That page is also reachable from the Contacts <fa icon="users" /> page
|
||||||
|
though the invitation <fa icon="envelope-open-text" /> icon.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 class="font-bold text-xl">Advanced</h1>
|
||||||
|
The following are optional steps for even more functionality:
|
||||||
<!-- eslint-disable prettier/prettier -->
|
<!-- eslint-disable prettier/prettier -->
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<h1 class="font-bold text-xl">Install</h1>
|
<h1 class="font-bold text-xl">Install</h1>
|
||||||
@@ -25,25 +43,36 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="font-bold text-xl">Add Contact & Register</h1>
|
<h1 class="font-bold text-xl">Add Contact & Register</h1>
|
||||||
|
<p>
|
||||||
|
You share even more information such as your picture and name when
|
||||||
|
you share with your QR code at these links: <fa icon="qrcode" /> Scanning
|
||||||
|
those with your cameras will automatically register people and add them
|
||||||
|
to each other's contact lists.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The following are more detailed manual steps:
|
||||||
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
3) Have them follow their yellow prompts.
|
3) Have them follow their yellow prompts.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
4) Add them to your contacts <fa icon="users" />
|
4) Scan their QR, or have them tap on it to copy their info and send it to you.
|
||||||
|
Then you can add them to your Contacts <fa icon="users" />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
5) Register them <fa icon="person-circle-question" />
|
5) You can register them at their info page <fa icon="circle-info" />
|
||||||
|
and click on the register button <fa icon="person-circle-question" />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
6) Add yourself to their contacts <fa icon="users" />
|
6) Add yourself to their Contacts <fa icon="users" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="font-bold text-xl">Enable Notifications</h1>
|
<h1 class="font-bold text-xl">Enable Notifications</h1>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
7) Enable notifications from <fa icon="circle-user" />
|
7) Enable notifications from the Account page <fa icon="circle-user" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<!-- New Project -->
|
<!-- New Project -->
|
||||||
<button
|
<button
|
||||||
v-if="isRegistered"
|
v-if="isRegistered"
|
||||||
class="fixed right-6 top-12 text-center text-4xl leading-none bg-blue-600 text-white w-14 py-2.5 rounded-full"
|
class="fixed right-6 top-12 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full"
|
||||||
@click="createInvite()"
|
@click="createInvite()"
|
||||||
>
|
>
|
||||||
<fa icon="plus" class="fa-fw"></fa>
|
<fa icon="plus" class="fa-fw"></fa>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<th class="py-2">ID</th>
|
<th class="py-2">ID</th>
|
||||||
<th class="py-2">Notes</th>
|
<th class="py-2">Notes</th>
|
||||||
<th class="py-2">Expires At</th>
|
<th class="py-2">Expires At</th>
|
||||||
<th class="py-2">Redeemed By</th>
|
<th class="py-2">Redeemed</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
class="border-t"
|
class="border-t"
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
class="py-2 text-center text-blue-500"
|
class="py-2 text-center text-blue-500 cursor-pointer"
|
||||||
@click="copyInviteAndNotify(invite.inviteIdentifier, invite.jwt)"
|
@click="copyInviteAndNotify(invite.inviteIdentifier, invite.jwt)"
|
||||||
title="{{ inviteLink(invite.jwt) }}"
|
title="{{ inviteLink(invite.jwt) }}"
|
||||||
>
|
>
|
||||||
@@ -58,7 +58,16 @@
|
|||||||
{{ invite.expiresAt.substring(0, 10) }}
|
{{ invite.expiresAt.substring(0, 10) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="py-2 text-center">
|
<td class="py-2 text-center">
|
||||||
|
{{ invite.redeemedAt?.substring(0, 10) }}
|
||||||
|
<br />
|
||||||
{{ getTruncatedRedeemedBy(invite.redeemedBy) }}
|
{{ getTruncatedRedeemedBy(invite.redeemedBy) }}
|
||||||
|
<br />
|
||||||
|
<fa
|
||||||
|
v-if="invite.redeemedBy && !contactsRedeemed[invite.redeemedBy]"
|
||||||
|
icon="plus"
|
||||||
|
class="bg-green-600 text-white px-1 py-1 rounded-full cursor-pointer"
|
||||||
|
@click="addNewContact(invite.redeemedBy)"
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<fa
|
<fa
|
||||||
@@ -70,6 +79,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<ContactNameDialog ref="contactNameDialog" />
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="mt-6 text-center">No invites found.</p>
|
<p v-else class="mt-6 text-center">No invites found.</p>
|
||||||
</section>
|
</section>
|
||||||
@@ -79,11 +89,12 @@ import axios from "axios";
|
|||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
|
||||||
import { db, retrieveSettingsForActiveAccount } from "../db";
|
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, NotificationIface } from "@/constants/app";
|
||||||
|
import { db, retrieveSettingsForActiveAccount } from "@/db";
|
||||||
import { createInviteJwt, getHeaders } from "@/libs/endorserServer";
|
import { createInviteJwt, getHeaders } from "@/libs/endorserServer";
|
||||||
|
|
||||||
interface Invite {
|
interface Invite {
|
||||||
@@ -91,11 +102,12 @@ interface Invite {
|
|||||||
expiresAt: string;
|
expiresAt: string;
|
||||||
jwt: string;
|
jwt: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
|
redeemedAt: string | null;
|
||||||
redeemedBy: string | null;
|
redeemedBy: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { QuickNav, TopMessage, InviteDialog },
|
components: { ContactNameDialog, QuickNav, TopMessage, InviteDialog },
|
||||||
})
|
})
|
||||||
export default class InviteOneView extends Vue {
|
export default class InviteOneView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
@@ -103,6 +115,7 @@ export default class InviteOneView extends Vue {
|
|||||||
invites: Invite[] = [];
|
invites: Invite[] = [];
|
||||||
activeDid: string = "";
|
activeDid: string = "";
|
||||||
apiServer: string = "";
|
apiServer: string = "";
|
||||||
|
contactsRedeemed = {};
|
||||||
isRegistered: boolean = false;
|
isRegistered: boolean = false;
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
@@ -119,6 +132,17 @@ export default class InviteOneView extends Vue {
|
|||||||
{ headers },
|
{ headers },
|
||||||
);
|
);
|
||||||
this.invites = response.data.data;
|
this.invites = response.data.data;
|
||||||
|
|
||||||
|
const baseContacts = await db.contacts.toArray();
|
||||||
|
for (const invite of this.invites) {
|
||||||
|
const contact = baseContacts.find(
|
||||||
|
(contact) => contact.did === invite.redeemedBy,
|
||||||
|
);
|
||||||
|
if (contact) {
|
||||||
|
this.contactsRedeemed[invite.redeemedBy] = contact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("contactsRedeemed", this.contactsRedeemed);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching invites:", error);
|
console.error("Error fetching invites:", error);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
@@ -140,6 +164,9 @@ 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]) {
|
||||||
|
return this.contactsRedeemed[redeemedBy].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)}`;
|
||||||
}
|
}
|
||||||
@@ -215,6 +242,7 @@ export default class InviteOneView extends Vue {
|
|||||||
expiresAt: expiresAt,
|
expiresAt: expiresAt,
|
||||||
jwt: inviteJwt,
|
jwt: inviteJwt,
|
||||||
notes: notes,
|
notes: notes,
|
||||||
|
redeemedAt: null,
|
||||||
redeemedBy: null,
|
redeemedBy: null,
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -229,6 +257,28 @@ export default class InviteOneView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addNewContact(did) {
|
||||||
|
(this.$refs.contactNameDialog as ContactNameDialog).open((name) => {
|
||||||
|
// the person obviously registered themselves and this user already granted visibility, so we just add them
|
||||||
|
const contact = {
|
||||||
|
did: did,
|
||||||
|
name: name,
|
||||||
|
registered: true,
|
||||||
|
};
|
||||||
|
db.contacts.add(contact);
|
||||||
|
this.contactsRedeemed[did] = contact;
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Contact Added",
|
||||||
|
text: `${name} has been added to your contacts.`,
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
deleteInvite(inviteId: string, notes: string) {
|
deleteInvite(inviteId: string, notes: string) {
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<!-- New Project -->
|
<!-- New Project -->
|
||||||
<button
|
<button
|
||||||
v-if="isRegistered && showProjects"
|
v-if="isRegistered && showProjects"
|
||||||
class="fixed right-6 top-24 text-center text-4xl leading-none bg-blue-600 text-white w-14 py-2.5 rounded-full"
|
class="fixed right-6 top-24 text-center text-4xl leading-none bg-green-600 text-white w-14 py-2.5 rounded-full"
|
||||||
@click="onClickNewProject()"
|
@click="onClickNewProject()"
|
||||||
>
|
>
|
||||||
<fa icon="plus" class="fa-fw"></fa>
|
<fa icon="plus" class="fa-fw"></fa>
|
||||||
|
|||||||
Reference in New Issue
Block a user