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.
240 lines
7.0 KiB
240 lines
7.0 KiB
<template>
|
|
<!-- CONTENT -->
|
|
<section id="Content" class="p-6 pb-24">
|
|
<!-- Breadcrumb -->
|
|
<div id="ViewBreadcrumb" class="mb-8">
|
|
<h1 class="text-lg text-center font-light relative px-7">
|
|
<!-- Cancel -->
|
|
<router-link
|
|
:to="{ name: 'project' }"
|
|
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
|
|
><fa icon="chevron-left" class="fa-fw"></fa
|
|
></router-link>
|
|
[New/Edit] Plan
|
|
</h1>
|
|
</div>
|
|
|
|
<!-- Project Details -->
|
|
<!-- Image - (see design model) Empty -->
|
|
|
|
<div>
|
|
{{ errorMessage }}
|
|
</div>
|
|
|
|
<input
|
|
type="text"
|
|
placeholder="Project Name"
|
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
|
v-model="projectName"
|
|
/>
|
|
|
|
<textarea
|
|
placeholder="Description"
|
|
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
|
|
rows="5"
|
|
v-model="description"
|
|
></textarea>
|
|
<div class="text-xs text-slate-500 italic -mt-3 mb-4">
|
|
88/500 max. characters
|
|
</div>
|
|
|
|
<div class="mt-8">
|
|
<button
|
|
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
|
|
@click="onSaveProjectClick()"
|
|
>
|
|
Save Project
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
|
|
@click="onCancelClick()"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</section>
|
|
<div
|
|
class="hidden dismissable-alert bg-slate-100 p-5 rounded drop-shadow-lg absolute top-3 inset-x-3 transition-transform ease-in duration-300"
|
|
>
|
|
<button
|
|
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
|
|
>
|
|
<i class="fa-solid fa-xmark"></i>
|
|
</button>
|
|
<h4 class="font-bold pr-5">{{ alertTitle }}</h4>
|
|
<p>{{ alertMessage }}</p>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Options, Vue } from "vue-class-component";
|
|
import { AppString } from "@/constants/app";
|
|
import { db } from "../db";
|
|
import { accessToken, SimpleSigner } from "@/libs/crypto";
|
|
import * as didJwt from "did-jwt";
|
|
import { IIdentifier } from "@veramo/core";
|
|
import { useAppStore } from "@/store/app";
|
|
import { AxiosError } from "axios";
|
|
|
|
interface VerifiableCredential {
|
|
"@context": string;
|
|
"@type": string;
|
|
name: string;
|
|
description: string;
|
|
identifier?: string;
|
|
}
|
|
|
|
@Options({
|
|
components: {},
|
|
})
|
|
export default class NewEditProjectView extends Vue {
|
|
projectName = "";
|
|
description = "";
|
|
errorMessage = "";
|
|
alertTitle = "";
|
|
alertMessage = "";
|
|
|
|
projectId = localStorage.getItem("projectId") || "";
|
|
|
|
async created() {
|
|
if (this.projectId === "") {
|
|
console.log("This is a new project");
|
|
} else {
|
|
await db.open();
|
|
const num_accounts = await db.accounts.count();
|
|
if (num_accounts === 0) {
|
|
console.log("Problem! Should have a profile!");
|
|
} else {
|
|
const accounts = await db.accounts.toArray();
|
|
const identity = JSON.parse(accounts[0].identity);
|
|
this.LoadProject(identity);
|
|
}
|
|
}
|
|
}
|
|
|
|
async LoadProject(identity: IIdentifier) {
|
|
const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER;
|
|
// eslint-disable-next-line prettier/prettier
|
|
const url = endorserApiServer + "/api/claim/byHandle/" + encodeURIComponent(this.projectId);
|
|
const token = await accessToken(identity);
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
Authorization: "Bearer " + token,
|
|
};
|
|
|
|
try {
|
|
const resp = await this.axios.get(url, { headers });
|
|
console.log(resp.status, resp.data);
|
|
if (resp.status === 200) {
|
|
const claim = resp.data.claim;
|
|
this.projectName = claim.name;
|
|
this.description = claim.description;
|
|
}
|
|
} catch (error) {
|
|
console.log(error);
|
|
}
|
|
}
|
|
|
|
private async SaveProject(identity: IIdentifier) {
|
|
// Make a claim
|
|
const vcClaim: VerifiableCredential = {
|
|
"@context": "https://schema.org",
|
|
"@type": "PlanAction",
|
|
name: this.projectName,
|
|
description: this.description,
|
|
identifier: this.projectId || undefined,
|
|
};
|
|
if (this.projectId) {
|
|
vcClaim.identifier = this.projectId;
|
|
}
|
|
// Make a payload for the claim
|
|
const vcPayload = {
|
|
sub: "PlanAction",
|
|
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 !== "undefined" &&
|
|
identity.keys[0].privateKeyHex !== null
|
|
) {
|
|
// eslint-disable-next-line
|
|
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 endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER;
|
|
const url = endorserApiServer + "/api/v2/claim";
|
|
const token = await accessToken(identity);
|
|
const headers = {
|
|
"Content-Type": "application/json",
|
|
Authorization: "Bearer " + token,
|
|
};
|
|
|
|
try {
|
|
const resp = await this.axios.post(url, payload, { headers });
|
|
console.log("Got resp data:", resp.data);
|
|
if (resp.data?.success?.fullIri) {
|
|
this.errorMessage = "";
|
|
useAppStore().setProjectId(resp.data.success.fullIri);
|
|
setTimeout(
|
|
function (that: Vue) {
|
|
console.log("THAT:", localStorage.getItem("projectId"));
|
|
const route = {
|
|
name: "project",
|
|
};
|
|
console.log(route);
|
|
that.$router.push(route);
|
|
},
|
|
2000,
|
|
this
|
|
);
|
|
}
|
|
} catch (error) {
|
|
let userMessage = "There was an error. See logs for more info.";
|
|
const serverError = error as AxiosError;
|
|
if (serverError) {
|
|
if (serverError.message) {
|
|
userMessage = serverError.message; // This is info for the user.
|
|
} else {
|
|
console.log("Server gave an error:", serverError);
|
|
}
|
|
} else {
|
|
console.log("Here's the full error trying to save the claim:", error);
|
|
}
|
|
// Now set that error for the user to see.
|
|
this.errorMessage = userMessage;
|
|
}
|
|
}
|
|
}
|
|
|
|
public async onSaveProjectClick() {
|
|
await db.open();
|
|
const num_accounts = await db.accounts.count();
|
|
if (num_accounts === 0) {
|
|
console.log("Problem! Should have a profile!");
|
|
} else {
|
|
const accounts = await db.accounts.toArray();
|
|
const identity = JSON.parse(accounts[0].identity);
|
|
this.SaveProject(identity);
|
|
}
|
|
}
|
|
|
|
public onCancelClick() {
|
|
this.$router.back();
|
|
}
|
|
}
|
|
</script>
|
|
|