timesafari
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

453 lines
14 KiB

<template>
<!-- QUICK NAV -->
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200">
<ul class="flex text-2xl p-2 gap-2">
<!-- Home Feed -->
2 years ago
<li class="basis-1/5 rounded-md text-slate-500">
<router-link :to="{ name: 'home' }" class="block text-center py-3 px-1">
<fa icon="house-chimney" class="fa-fw"></fa>
</router-link>
2 years ago
</li>
<!-- Search -->
2 years ago
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'discover' }"
class="block text-center py-3 px-1"
>
<fa icon="magnifying-glass" class="fa-fw"></fa>
</router-link>
2 years ago
</li>
<!-- Projects -->
2 years ago
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'projects' }"
class="block text-center py-3 px-1"
>
<fa icon="folder-open" class="fa-fw"></fa>
</router-link>
2 years ago
</li>
<!-- Contacts -->
2 years ago
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'contacts' }"
class="block text-center py-3 px-1"
>
<fa icon="users" class="fa-fw"></fa>
</router-link>
2 years ago
</li>
<!-- Profile -->
2 years ago
<li class="basis-1/5 rounded-md bg-slate-400 text-white">
<router-link
:to="{ name: 'account' }"
class="block text-center py-3 px-1"
>
<fa icon="circle-user" class="fa-fw"></fa>
</router-link>
2 years ago
</li>
</ul>
</nav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<!-- Heading -->
2 years ago
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Your Identity
</h1>
<div class="flex justify-between py-2">
<span />
<span>
<router-link
:to="{ name: 'help' }"
class="text-xs uppercase bg-blue-500 text-white px-1.5 py-1 rounded-md ml-1"
>
Help
</router-link>
</span>
</div>
<!-- Friend referral requirement notice -->
2 years ago
<div
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"
>
<p class="mb-4">
<b>Important:</b> before you can create a new project or commit time to
one, you need a friend to register you.
</p>
<button
id="btnShowQR"
class="inline-block text-md uppercase bg-amber-600 text-white px-4 py-2 rounded-md"
>
Share Your ID
</button>
</div>
<!-- Identity Details -->
<div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4">
<h2 class="text-xl font-semibold mb-2">{{ firstName }} {{ lastName }}</h2>
<div class="text-slate-500 text-sm font-bold">ID</div>
2 years ago
<div
class="text-sm text-slate-500 flex justify-between items-center mb-1"
>
<span
><code>{{ address }}</code>
<button @click="copy(address)">
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
</button>
</span>
2 years ago
<span>
<button
class="text-xs uppercase bg-slate-500 text-white px-1.5 py-1 rounded-md"
>
<fa icon="share-nodes" class="fa-fw"></fa>
2 years ago
</button>
<button
class="text-xs uppercase bg-slate-500 text-white px-1.5 py-1 rounded-md ml-1"
2 years ago
>
<fa icon="qrcode" class="fa-fw"></fa>
2 years ago
</button>
</span>
</div>
<div class="text-slate-500 text-sm font-bold">Public Key (base 64)</div>
<div class="text-sm text-slate-500 mb-1">
<span>
<code>{{ publicBase64 }}</code>
<button @click="copy(publicBase64)">
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
</button>
</span>
</div>
<div class="text-slate-500 text-sm font-bold">Public Key (hex)</div>
<div class="text-sm text-slate-500 mb-1">
<span>
<code>{{ publicHex }}</code>
<button @click="copy(publicHex)">
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
</button>
</span>
</div>
<div class="text-slate-500 text-sm font-bold">Derivation Path</div>
<div class="text-sm text-slate-500 mb-1">
2 years ago
<span
><code>{{ derivationPath }}</code>
<button @click="copy(derivationPath)">
<fa icon="copy" class="text-slate-400 fa-fw ml-1"></fa>
</button>
</span>
</div>
</div>
<router-link
:to="{ name: 'new-edit-account' }"
2 years ago
class="block text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-8"
>
2 years ago
Edit Identity
</router-link>
2 years ago
<h3 class="text-sm uppercase font-semibold mb-3">Data</h3>
2 years ago
<a
href=""
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
2 years ago
Backup Identifier Seed
</a>
2 years ago
<a
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
2 years ago
@click="exportDatabase()"
2 years ago
>
2 years ago
Download Settings & Contacts (excluding Identifier Data)
</a>
<a ref="downloadLink" />
<!-- QR code popup -->
<dialog id="dlgQR" class="backdrop:bg-black/75 rounded-md">
<form method="dialog">
2 years ago
<div class="text-slate-500 text-center">
<b>ID:</b> <code>did:peer:kl45kj41lk451kl3</code>
</div>
<img src="img/sample-qr-code.png" class="w-full mb-3" />
2 years ago
<button
value="cancel"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-2"
>
Copy to Clipboard
</button>
<button
value="cancel"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
>
Close
</button>
</form>
</dialog>
<h3 class="text-sm uppercase font-semibold mb-3">Advanced</h3>
<div class="flex">
<button
href=""
class="text-center text-md text-white px-1.5 py-2 rounded-md mb-6"
v-bind:class="showContactGivesClassNames()"
@click="toggleShowContactAmounts()"
>
{{ showContactGives ? "Showing" : "Hiding" }}
amounts given with contacts (Click to
{{ showContactGives ? "hide" : "show" }})
</button>
</div>
<div class="flex">
<button
class="text-center text-md text-blue-500 px-1.5 py-2"
@click="checkLimits()"
>
Check Limits
</button>
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
<span class="font-bold">Rate Limits</span>
<p>
You have done {{ limits.doneClaimsThisWeek }} claims out of
{{ limits.maxClaimsPerWeek }} for this week. Your claims counter
resets at {{ readableTime(limits.nextWeekBeginDateTime) }}
</p>
<p>
You have done {{ limits.doneRegistrationsThisMonth }} registrations
out of {{ limits.maxRegistrationsPerMonth }} for this month. Your
registrations counter resets at
{{ readableTime(limits.nextMonthBeginDateTime) }}
</p>
</div>
</div>
<div v-bind:class="computedAlertClassNames()">
<button
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
@click="onClickClose()"
>
<fa icon="xmark"></fa>
</button>
<h4 class="font-bold pr-5">{{ alertTitle }}</h4>
<p>{{ alertMessage }}</p>
</div>
</section>
</template>
<script lang="ts">
2 years ago
import "dexie-export-import";
import { Options, Vue } from "vue-class-component";
import { useClipboard } from "@vueuse/core";
import { db, accountsDB } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import {
accessToken,
deriveAddress,
generateSeed,
newIdentifier,
} from "@/libs/crypto";
import { AppString } from "@/constants/app";
//import { testServerRegisterUser } from "../test";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Buffer = require("buffer/").Buffer;
interface RateLimits {
doneClaimsThisWeek: string;
doneRegistrationsThisMonth: string;
maxClaimsPerWeek: string;
maxRegistrationsPerMonth: string;
nextMonthBeginDateTime: string;
nextWeekBeginDateTime: string;
}
@Options({
2 years ago
components: {},
})
export default class AccountViewView extends Vue {
address = "";
derivationPath = "";
firstName = "";
lastName = "";
mnemonic = "";
publicHex = "";
publicBase64 = "";
limits: RateLimits | null = null;
showContactGives = false;
copy = useClipboard().copy;
readableTime(timeStr: string) {
return timeStr.substring(0, timeStr.indexOf("T"));
}
// 'created' hook runs when the Vue instance is first created
async created() {
// Uncomment to register this user on the test server.
// To manage within the vue devtools browser extension https://devtools.vuejs.org/
// assign this to a class variable, eg. "registerThisUser = testServerRegisterUser",
// select a component in the extension, and enter in the console: $vm.ctx.registerThisUser()
//testServerRegisterUser();
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
if (settings) {
this.firstName = settings.firstName || "";
this.lastName = settings.lastName || "";
this.showContactGives = !!settings.showContactGivesInline;
}
await accountsDB.open();
const numAccounts = await accountsDB.accounts.count();
if (numAccounts === 0) {
let privateHex = "";
this.mnemonic = generateSeed();
[this.address, privateHex, this.publicHex, this.derivationPath] =
deriveAddress(this.mnemonic);
const newId = newIdentifier(
this.address,
this.publicHex,
privateHex,
this.derivationPath
);
await accountsDB.accounts.add({
dateCreated: new Date().toISOString(),
derivationPath: this.derivationPath,
identity: JSON.stringify(newId),
mnemonic: this.mnemonic,
publicKeyHex: newId.keys[0].publicKeyHex,
});
}
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
this.address = identity.did;
this.publicHex = identity.keys[0].publicKeyHex;
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
this.derivationPath = identity.keys[0].meta.derivationPath;
} catch (err) {
this.alertMessage =
"Clear your cache and start over (after data backup). See console log for more info.";
console.log("Telling user to clear cache because:", err);
this.alertTitle = "Error Creating Account";
this.isAlertVisible = true;
}
}
public async toggleShowContactAmounts() {
this.showContactGives = !this.showContactGives;
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
if (settings) {
db.settings.update(MASTER_SETTINGS_KEY, {
showContactGivesInline: this.showContactGives,
});
}
} catch (err) {
this.alertMessage =
"Clear your cache and start over (after data backup). See console log for more info.";
console.log("Telling user to clear cache because:", err);
this.alertTitle = "Error Creating Account";
this.isAlertVisible = true;
}
}
2 years ago
public async exportDatabase() {
try {
const blob = await db.export({ prettyJson: true });
const url = URL.createObjectURL(blob);
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
downloadAnchor.href = url;
downloadAnchor.download = db.name + "-backup.json";
downloadAnchor.click();
URL.revokeObjectURL(url);
this.alertTitle = "Download Started";
this.alertMessage = "See your downloads directory for the backup.";
this.isAlertVisible = true;
} catch (error) {
this.alertTitle = "Export Error";
this.alertMessage = "See console logs for more info.";
this.isAlertVisible = true;
console.error("Export Error:", error);
}
}
async checkLimits() {
const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER;
const url = endorserApiServer + "/api/report/rateLimits";
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const identity = JSON.parse(accounts[0].identity);
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
try {
const resp = await this.axios.get(url, { headers });
if (resp.status === 200) {
this.limits = resp.data;
} else {
this.alertTitle = "Error from Server";
console.log("Bad response retrieving limits: ", resp.data);
if (resp.data.error?.message) {
this.alertMessage = resp.data.error?.message;
} else {
this.alertMessage = "Bad server response of " + resp.status;
}
this.isAlertVisible = true;
}
} catch (err) {
this.alertTitle = "Error from Server";
this.alertMessage = err as string;
this.isAlertVisible = true;
}
}
public showContactGivesClassNames() {
return {
"bg-slate-900": !this.showContactGives,
"bg-green-600": this.showContactGives,
};
}
alertMessage = "";
alertTitle = "";
isAlertVisible = false;
public onClickClose() {
this.isAlertVisible = false;
this.alertTitle = "";
this.alertMessage = "";
}
public computedAlertClassNames() {
return {
hidden: !this.isAlertVisible,
"dismissable-alert": true,
"bg-slate-100": true,
"p-5": true,
rounded: true,
"drop-shadow-lg": true,
absolute: true,
"top-3": true,
"inset-x-3": true,
"transition-transform": true,
"ease-in": true,
"duration-300": true,
};
}
}
</script>